Skip to Content
GuidesCreating and Managing Apps

Creating and Managing Apps

An App is the primary deployment unit in kubenest. This guide covers the most common App operations from first deploy through day-two management: updating components, scaling, pausing, resuming, and rolling back. All examples use curl against the REST API — the UI performs the same calls.

Before reading this guide, make sure you have a cluster registered and a project created. If not, start with the First Deployment walkthrough.


Deploying a single-component app (image mode)

The simplest App has one workload component that runs a pre-built container image. This is the right starting point if your image is already in a registry.

create a simple nginx app
curl -X POST https://api.your-domain.com/api/v1/apps \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "my-app", "project_id": "b2c3d4e5-0002-0000-0000-000000000000", "components": [ { "name": "web", "type": "workload", "workload_spec": { "build_mode": "image", "image": "nginx:alpine", "replicas": 2, "port": 80, "ingress": { "enabled": true, "host": "my-app.your-domain.com" } } } ] }'

The backend responds immediately with phase: pending. The operator begins reconciling asynchronously. Watch progress:

watch status
curl -N -H "Authorization: Bearer $TOKEN" \ "https://api.your-domain.com/api/v1/events/stream?namespace=my-project&name=my-app"

A healthy deploy produces this event sequence:

data: {"type":"app.status","name":"my-app","phase":"deploying","message":"Waiting for ArgoCD sync"} data: {"type":"app.status","name":"my-app","phase":"deploying","message":"ArgoCD syncing"} data: {"type":"app.status","name":"my-app","phase":"running","message":""}

Once running, the ingress is live with TLS provisioned automatically by cert-manager.

Key workload_spec fields:

FieldTypeDescription
build_modeimage | dockerfile | buildpackHow to obtain the container image
imagestringFull image reference (required for image mode)
replicasintegerNumber of pod replicas (default: 1)
portintegerPort the container listens on
ingress.enabledbooleanWhether to create an ingress resource
ingress.hoststringHostname for the ingress rule
envarrayEnvironment variables — plain values or export_ref
commandarrayOverride the container entrypoint
resourcesobjectCPU/memory requests and limits

Adding a Postgres addon and wiring the connection string

The exportRef mechanism lets a workload consume values that an addon generates at deploy time — connection strings, passwords, hostnames — without you ever seeing or hardcoding them. The following example creates an App with two components: a PostgreSQL addon and an API workload that reads the connection string via exportRef.

two-component app with export wiring
curl -X POST https://api.your-domain.com/api/v1/apps \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "my-api", "project_id": "b2c3d4e5-0002-0000-0000-000000000000", "components": [ { "name": "postgres", "type": "addon", "addon_spec": { "type": "postgresql", "chart": { "repo": "https://charts.bitnami.com/bitnami", "name": "postgresql", "version": "13.4.4" }, "values": { "auth": { "database": "myapp", "username": "myapp" } } } }, { "name": "api", "type": "workload", "depends_on": ["postgres"], "workload_spec": { "build_mode": "image", "image": "myorg/my-api:v1.0.0", "replicas": 2, "port": 8000, "env": [ { "name": "DATABASE_URL", "export_ref": { "component": "postgres", "export_key": "connection_string" } }, { "name": "APP_ENV", "value": "production" } ], "ingress": { "enabled": true, "host": "api.your-domain.com" } } } ], "timeout": "15m" }'

The depends_on: ["postgres"] field tells the operator to deploy the postgres addon first, wait for its exports to be published, and only then render and deploy the api workload. Without depends_on, both components start concurrently — which works if your application handles a missing database gracefully at startup, but often causes unnecessary restarts on the first deploy.

The export_ref.export_key value connection_string is a kubenest convention for addons that have an AddonDefinition entry. The available keys for each addon type are listed under Addons.

You can also wire an environment variable from an existing standalone addon instance that is not part of this App — a shared database, for example. Use addon_instance_id instead of component:

{ "name": "DATABASE_URL", "export_ref": { "addon_instance_id": "c3d4e5f6-...", "export_key": "connection_string" } }

The addon instance must be in the same project as the App.


Updating an app

Use PATCH /api/v1/apps/{namespace}/{name} to modify an existing App. The PATCH body accepts a components array containing mutations: you can add new components, remove existing ones, or patch individual fields within a component.

Patching a component (change image tag)

update image tag
curl -X PATCH https://api.your-domain.com/api/v1/apps/my-project/my-api \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "components": [ { "name": "api", "op": "patch", "workload_spec": { "image": "myorg/my-api:v1.1.0" } } ] }'

Fields omitted from the patch are left unchanged. The operator re-renders the Helm values with the new image, commits to Git, and ArgoCD performs a rolling update.

Adding a component to an existing app

add a Redis addon
curl -X PATCH https://api.your-domain.com/api/v1/apps/my-project/my-api \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "components": [ { "name": "cache", "op": "add", "type": "addon", "addon_spec": { "type": "redis", "chart": { "repo": "https://charts.bitnami.com/bitnami", "name": "redis", "version": "19.6.4" }, "values": { "architecture": "standalone" } } } ] }'

Removing a component

remove a component
curl -X PATCH https://api.your-domain.com/api/v1/apps/my-project/my-api \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "components": [ { "name": "cache", "op": "remove" } ] }'

Removing a component that another component’s exportRef depends on will be rejected with a 409 Conflict. You must first update the dependent component to remove the exportRef, then remove the source component in a second PATCH.

For example, if api reads REDIS_URL from cache, you must first patch api to remove the REDIS_URL exportRef entry, and only then remove cache.


Scaling

To change the replica count of a workload component, use the dedicated scale endpoint rather than a full PATCH. This is a lightweight operation — it does not trigger a full reconcile of all components.

scale a component
curl -X POST https://api.your-domain.com/api/v1/apps/my-project/my-api/scale \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "component_name": "api", "replicas": 4 }'

The operator updates only the affected component’s Helm values and ArgoCD Application. The other components are not touched. The scale change is recorded in the Deployment history.


Pausing and resuming

Pausing an App sets every workload component’s replica count to zero, preserving the deployment configuration. This stops all compute costs for the App while retaining the namespace, the Kubernetes resources, and the addon state (databases remain running).

pause
curl -X POST https://api.your-domain.com/api/v1/apps/my-project/my-api/pause \ -H "Authorization: Bearer $TOKEN"

The backend:

  1. Reads the current replica count from each workload component.
  2. Saves the snapshot as pre_pause_replicas on the App record.
  3. Issues a scale-to-zero for each workload component.

The App’s phase transitions to paused. Addon components (PostgreSQL, Redis, etc.) are not scaled — they continue running so that data is preserved.

Resume restores the pre-pause replica counts:

resume
curl -X POST https://api.your-domain.com/api/v1/apps/my-project/my-api/resume \ -H "Authorization: Bearer $TOKEN"

The App re-enters the deployingrunning flow as the replicas come back up.

If you scale a component while the App is paused (e.g., from 2 to 4 via the scale endpoint), the resume operation restores the pre-pause count, not the count you set while paused. The scale change while paused is effectively discarded by resume. To change the replica count permanently, wait until the App is running and then use the scale endpoint.


Rollback

Every state-changing operation on an App records a Deployment row in the backend. You can list this history and restore any prior configuration.

List deployment history

list deployments
curl -H "Authorization: Bearer $TOKEN" \ "https://api.your-domain.com/api/v1/apps/my-project/my-api/deployments" | jq '.[] | {id, created_at, phase, message}'

Example output:

[ { "id": "d5e6f7a8-...", "created_at": "2026-06-11T14:32:00Z", "phase": "success", "message": "Updated api image to v1.1.0" }, { "id": "c4d5e6f7-...", "created_at": "2026-06-11T09:10:00Z", "phase": "success", "message": "Added Redis addon" }, { "id": "b3c4d5e6-...", "created_at": "2026-06-10T17:45:00Z", "phase": "success", "message": "Initial deploy" } ]

Roll back to a specific deployment

rollback
curl -X POST https://api.your-domain.com/api/v1/apps/my-project/my-api/rollback \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"deployment_id": "b3c4d5e6-..."}'

Alternatively, roll back by a relative revision number (useful in automation):

rollback by revision
curl -X POST https://api.your-domain.com/api/v1/apps/my-project/my-api/rollback \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"revision": -1}'

revision: -1 means “one revision before the current one”; -2 means two revisions back; and so on.

A rollback is not a git revert — it is a new forward deployment to the prior spec. The GitOps history records the rollback as a new commit, giving you a complete linear audit trail. See GitOps and Drift Detection for the full mechanics.

Rolling back an App that contains addon components will also roll back those addons’ Helm chart versions and values. If the prior spec had an older chart version for a PostgreSQL addon, rolling back will trigger a Helm downgrade, which can be schema-incompatible. Always check the deployment diff before rolling back an App with stateful addons.


Deleting an app

Deleting an App removes all its components from the cluster (Helm uninstalls, ArgoCD Applications deleted) and removes the corresponding Git paths from the GitOps repository.

delete app
curl -X DELETE https://api.your-domain.com/api/v1/apps/my-project/my-api \ -H "Authorization: Bearer $TOKEN"

The delete is async: the App record transitions to deleting while the operator cleans up, then the record is removed. Deployment history is retained in the backend database after the App is deleted, queryable via the deployments endpoint until the record is purged by the retention policy.

Addon components that were part of the App are also deleted by default, including their Kubernetes namespaced resources and Persistent Volume Claims. If you want to preserve the data volume from a PostgreSQL addon, detach it as a standalone AddonInstance before deleting the App. See Managing Addons for how to do this.


See also:

Last updated on