Kubernetes (Helm chart)
The chart is published as an OCI artifact at oci://registry.tinyfactory.ai/tinyblox/charts/tinyguard, versioned with
each release (the chart version equals the provider version). It deploys one
workload — the provider — with a rendered configuration, a mounted secrets
file, probes wired to the health endpoints, and a locked-down security
context. The admin console is part of the same container and needs no
separate deployment.
Before you install
Section titled “Before you install”The chart creates no Secret, so secret material never lands in values.yaml.
Prepare these first, in this order (a Secret can only be created in a namespace
that exists, so the namespace comes first):
NS=tinyguardkubectl create namespace "$NS"
# 1. The pull secret for the private registry.kubectl -n "$NS" create secret docker-registry registry-pull \ --docker-server=registry.tinyfactory.ai \ --docker-username=<user> --docker-password=<token>
# 2. The provider's secrets file: generate it once with nothing but the image,# and keep a copy. Losing the encryption key makes stored secrets unreadable.IMAGE=registry.tinyfactory.ai/tinyblox/guard:0.3.2docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" -w /work \ "$IMAGE" generate-secrets --output secrets.tomlkubectl -n "$NS" create secret generic tinyguard-secrets \ --from-file=secrets.toml=secrets.toml
# 3. With storage.mode=postgres: the database login's password, as a file.kubectl -n "$NS" create secret generic tinyguard-pg \ --from-literal=password='…'The first administrator is seeded on the first start: set
bootstrapAdmin.email, and optionally a Secret holding a password hash (see
Deployment); without one, a password is
generated and printed once to the pod’s log.
Install
Section titled “Install”helm registry login registry.tinyfactory.aihelm upgrade --install tinyguard oci://registry.tinyfactory.ai/tinyblox/charts/tinyguard \ --version 0.3.2 \ --namespace "$NS" \ --set publicUrl=idp.example.com \ --set image.digest=sha256:…Always deploy by digest: a tag can be re-pushed, a digest cannot. Read a
release’s digest with
docker buildx imagetools inspect registry.tinyfactory.ai/tinyblox/guard:0.3.2.
Values
Section titled “Values”| Key | Default | Meaning |
|---|---|---|
publicUrl |
idp.example.com |
The public origin; also the OIDC issuer and console redirect target. |
image.repository |
tinyblox/guard |
Repository in registry.tinyfactory.ai. |
image.digest / image.tag |
'' / latest |
Digest wins when set. Deploy by digest. |
replicaCount |
1 |
More than one requires storage.mode: postgres. |
server.scheme |
http |
http terminates TLS at the ingress. |
metrics.enabled / metrics.port |
true / 9090 |
The metrics listener, on its own port and never routed by the ingress. |
metrics.vmServiceScrape.* |
disabled | A VictoriaMetrics scrape object for it. |
postgres.* |
— | The database, below. Named as in every TinyBlox chart. |
podSecurityContext, securityContext |
non-root, read-only filesystem | The pod and container security settings; the defaults are the platform’s. |
settings.* |
— | Every other configuration setting, by its configuration key (below). |
extraEnv |
{} |
An environment variable the chart does not model; never a documented setting. |
server.docs.enabled |
false |
The built-in API documentation routes. |
storage.mode |
embedded |
embedded or postgres. |
storage.embedded.size |
10Gi |
The PVC for embedded storage. |
secrets.secretName / .key |
tinyguard-secrets / secrets.toml |
The mounted secrets file. |
bootstrapAdmin.* |
— | The first-boot administrator’s email and password Secret. |
ingress.* |
enabled | Class, cert-manager issuer (added as the cert-manager.io/cluster-issuer annotation), TLS secret. |
networkPolicy.enabled |
true |
Allow the HTTP port only from the ingress namespace. |
With ingress.enabled=false the chart exposes only a ClusterIP service:
there is no console access from outside until you expose it yourself.
Storage modes
Section titled “Storage modes”Embedded (the default) puts the database on a PersistentVolume with
ReadWriteOnce. It is single-node — the chart refuses replicaCount > 1 in
this mode — and the PVC carries helm.sh/resource-policy: keep, so
helm uninstall does not take the tenant and credential records with it.
Postgres suits anything that scales, shares a database server, or needs
managed backups. Replicas roll out without a gap (RollingUpdate, never
fewer than the requested number ready), which is the other half of why
replicaCount can go above one only in this mode:
replicaCount: 3storage: mode: postgrespostgres: host: db.internal dbName: tinyguard schema: tinyguard # the schema this service owns user: tinyguard password: existingSecret: tinyguard-pg # a Secret you create; key `password` tlsMode: verify-full # the default; see below ca: existingSecret: pg-ca # a Secret with the CA bundle (key ca.crt) poolSize: 10 # per replica; null keeps the server's defaultThe password is a mounted file, read through GUARD_STORAGE_PASSWORD_FILE: it
is not in the environment, in kubectl describe pod, in the ConfigMap or in
values. The chart refuses a Postgres install without it. (secrets.toml still
carries the encryption key.)
Three values deserve a decision rather than a default:
tlsModeisverify-full. With nocathe image’s system trust store is used, which suits a server certified by a public authority. A server inside the cluster with a private authority needsca.existingSecret, a Secret holding the bundle that signed its certificate (keyca.crt, or setca.key).preferandrequireencrypt without checking who answered; they are for development.poolSizemultiplies byreplicaCount, and that product, plus a few connections for migrations, must fit under the server’smax_connections. See the connection budget.schemalets several components share one database. Give each its own.
With replicas sharing a store, the singleton duties (signing-key rotation, cleanup, the email and event pipelines) run on one replica at a time, chosen by a lease in the database, and move to another within about thirty seconds if that replica goes away.
Migrations
Section titled “Migrations”By default every pod applies pending schema steps when it starts, taking turns on a database lock. To keep the runtime login to data access only, have the chart run them as a hook before each install and upgrade, with a login that may change the schema:
postgres: autoMigrate: false # pods only check the schema is current migrateJob: enabled: true owner: user: tinyguard_owner password: existingSecret: tinyguard-pg-owner # key `password`The Job runs idp-server migrate with the owner login in place of the runtime
one (the chart refuses migrateJob.enabled with autoMigrate: true, which
would apply the schema twice).
A failed Job fails the release, visibly, instead of a crash loop in the pods; a
pod that starts against a schema that is behind refuses to serve and names the
command to run.
Values are validated
Section titled “Values are validated”The chart ships a values.schema.json that rejects any key it does not know, at
every level: a misspelt networkPolcy is an error, not a silent no-op. It also
checks enums (postgres.tlsMode), ports, the shape of image.digest and that
publicUrl carries no scheme. helm lint and helm install both run it.
Every setting is a value
Section titled “Every setting is a value”Each documented configuration setting is a named value under settings, spelled
as the configuration key, and the chart turns it into the right environment
variable (settings.hashing.argon2_m_cost becomes GUARD_HASHING_ARGON2_M_COST).
Nothing is passed as free text, so the schema checks the name, the type and the
shape of every one, and helm show values lists them all with their
documentation.
settings: tenants: enabled: true observability: enabled: true endpoint: http://otel-collector.observability:4318 service_name: tinyguard security: blacklist: [203.0.113.7] events: notify: slack: webhook_url: # a secret: a reference, never the value existingSecret: tinyguard-notify key: slack-webhooknull, or leaving a value out, keeps the server’s default.- A list is an array. The chart joins it with commas.
- A secret setting (a password, token or webhook) is never a literal: it names a
Secret and a key, the chart mounts it as a file, and the server reads it through
the
_FILEform of the variable. - The settings the chart names itself (
publicUrl,server.scheme,metrics.*,postgres.*) are not repeated undersettings. extraEnvremains for an environment variable the chart does not model. The schema refuses a documented setting there and tells you to usesettings.
The complete list, with defaults and the variable each becomes, is in the environment reference.
Probes, shutdown, and the security context
Section titled “Probes, shutdown, and the security context”- Liveness hits
/livezand readiness hits/readyz, which answers only when storage answers; a startup probe on/readyz(150 tries, two seconds apart: five minutes,startupProbe.failureThreshold) covers a slow first start. - The pod runs as
65532:65532with a read-only root filesystem, all capabilities dropped, no privilege escalation, andRuntimeDefaultseccomp. Writable paths are the data volume and a tmpfs at/tmp. - The service account token is not mounted (
automountServiceAccountToken: false) — the pod needs no Kubernetes API access. - A pod stops in two steps: a
preStopsleep (preStopSleepSeconds, 5 by default, the native sleep action, so Kubernetes 1.30 or newer) lets endpoint removal propagate before SIGTERM, then the server drains for up toserver.graceful_shutdown_seconds.terminationGracePeriodSecondsmust cover both, and the chart refuses a sleep that does not leave room. - A
NetworkPolicyallows the HTTP port only fromnetworkPolicy.ingressNamespaceand the metrics port only fromnetworkPolicy.metrics.scraperNamespace(defaultobservability). Egress is limited to DNS, the database (networkPolicy.egress.databasePort, optionally narrowed withdatabaseTo), HTTPS and the OTLP collector. Mail or another port needs a rule undernetworkPolicy.egress.extra. Your network plugin must enforce NetworkPolicies for this to matter. - Metrics scraping: set
metrics.vmServiceScrape.enabled=trueto render a VictoriaMetricsVMServiceScrapefor themetricsport, and put your VMAgent’s selector labels undermetrics.vmServiceScrape.labels. - Every object carries the standard labels, including
app.kubernetes.io/part-of: tinyblox.
Because the root filesystem is read-only and there is no shell in the image,
anything that expects to kubectl exec a shell will not work. Use the
endpoints and logs instead.
Gating the console behind sign-in
Section titled “Gating the console behind sign-in”The console is already gated: administrators authenticate through the provider the pod itself hosts, so there is nothing extra to deploy. If you want a second, infrastructure-level gate in front of the whole host (for example limiting the console to a corporate domain during a rollout), the chart’s ingress is the place to add it — deploy an authenticating proxy and route the ingress through it, the same pattern the design-system catalogue uses. That proxy can use TinyGuard itself as its OIDC provider.
Upgrades and rollback
Section titled “Upgrades and rollback”helm upgrade with a new digest is the whole procedure; the ConfigMap
checksum in the pod template rolls the pods when configuration changes. The
server applies storage migrations on boot and refuses to start on a failed
migration, so a bad upgrade fails visibly rather than serving half-migrated
data. helm rollback returns to the previous revision and digest.
Keep the secrets Secret out of the chart’s lifecycle: it is created out of band and survives upgrades, rollbacks, and uninstalls.