Addons and templates
An addon is a backing service: PostgreSQL, Redis, Kafka, Elasticsearch, RabbitMQ. Anything your application depends on but that is not itself your application.
What separates an addon from a workload is what it produces. A workload serves users. An addon produces exports — connection strings, hostnames, credentials — that workloads consume. That is the whole reason the concept exists: without it, deploying a database is half the job and somebody still has to wire the consumer to it by hand.
In an app
The common case. A component with an addon: key is a backing service rather than a container, and
it lives and dies with the app:
components:
api:
image: myorg/my-api:v1.2.0
port: 8000
env:
DATABASE_URL: ${postgres.dsn}
postgres:
addon: postgres@16
storage: 20Gikubenest deploy creates Postgres first because api reads a value from it, waits for the exports,
then starts api with DATABASE_URL already resolved. Pausing the app does not pause the addon —
databases keep running so data survives.
Standing on its own
When several apps share one database, or when its life should outlast any single app:
kubenest addon create shared-postgres --type postgres@16 --project acme-prod --storage 50GiReference it from any app in the same project:
env:
DATABASE_URL: ${addon:shared-postgres.dsn}The catalog
kubenest addon catalogpostgres 16, 15, 14 PostgreSQL
redis 7, 6 Redis
kafka 3 Apache KafkaEach entry pins a chart, its default values, and the export keys it publishes. To run something the catalog does not carry, name a chart directly:
components:
clickhouse:
chart: oci://registry-1.docker.io/bitnamicharts/clickhouse
version: 6.3.1
values:
auth:
username: analyticsExports
What postgres publishes:
| Key | Example |
|---|---|
dsn | postgresql://myapp:…@shared-postgres.acme-prod.svc.cluster.local:5432/myapp?sslmode=require |
host | shared-postgres.acme-prod.svc.cluster.local |
port | 5432 |
user, database | myapp |
Conventional aliases come too, so a container expecting the standard PostgreSQL environment names
needs no mapping: DATABASE_URL, PGHOST, PGPORT, PGUSER, PGDATABASE.
Each type has its own extractor and key set. Anything else falls back to service.url and
service.port.
kubenest addon show shared-postgresshared-postgres postgres@16 running 50Gi revision 3
exports
dsn postgresql://myapp:••••@shared-postgres.acme-prod…
host shared-postgres.acme-prod.svc.cluster.local
port 5432
user myapp
database myapp
used by
my-api/api DATABASE_URL
my-api/worker DATABASE_URL
reports/etl DATABASE_URLNothing ever prints a password. Secret-backed values are redacted in every response and every
command. They are resolved on the cluster, at deploy time, into the consuming container — never
through the control plane and never onto your terminal. kubenest addon password shared-postgres
reads it from the cluster directly, when you genuinely need it.
Changing one
Every values change or version bump is a numbered revision, so there is an audit trail and a way back.
kubenest addon set shared-postgres memory=1Gi --note "production load"
kubenest addon upgrade shared-postgres --to 17
kubenest addon revisions shared-postgres
kubenest addon rollback shared-postgres --to 2A rollback creates a new revision rather than deleting the ones in between.
Rolling an addon back to an older chart version is a Helm downgrade. Helm will do it, but many
database charts run schema migrations on upgrade that do not reverse. Snapshot the data first —
kubenest backup now — before rolling back anything stateful.
Deleting one
kubenest addon destroy shared-postgresIt refuses while an app still references it, naming what depends on it. The volume goes with it, so take a backup first if the data matters.
An addon that is a component of an app can be lifted out before you destroy the app, keeping the data:
kubenest addon detach my-api/postgres --as shared-postgresTemplates
A template is the shape of a working app with the varying parts pulled out: components, wiring and dependencies fixed, image tag and replica count and database name exposed as parameters.
Think of it the way you think of a Helm chart, one level up. A chart describes Kubernetes resources; a template describes a whole multi-component application.
Making one
From an app that already works — the natural way, because you have already debugged it:
kubenest template save my-api --as acme/api-service --version 1.0.0Wiring and ordering come along. The CLI proposes parameters for the fields that usually vary and you edit the list.
From a Helm chart — wrap any public chart so it can be deployed with a few named inputs instead of a values file:
kubenest template from-chart oci://…/postgresql --version 13.4.4 --as acme/postgresFrom a file — templates are portable, and this is what the registry ships:
kubenest template import ./api-service.yamlParameters
parameters:
image_tag:
type: string
required: true
binds: components.api.image
replicas:
type: integer
default: 2
binds: components.api.replicas
db_password:
type: string
generate: password
binds: components.postgres.values.auth.passwordgenerate fills a parameter at deploy time when nobody supplies one — password, hex32, hex16,
uuid. That is the pattern for secrets: nobody has to invent one, and nobody has to store one.
Deploying one
kubenest template deploy acme/api-service \
--project acme-staging \
--set image_tag=v1.2.0 \
--set replicas=3The result is an ordinary app. Patch it, scale it, roll it back, save it as a new template.
The registry
Templates published by us and by other users:
kubenest template search postgres
kubenest template deploy community/postgres-ha --project acme-prodDeploying from the registry installs a local copy first, so a template you are running cannot change under you when its author publishes a new version.
Next: Day 2 · Architecture