Deploying Apps
Kipper deploys applications as Kubernetes Deployments with a Service and Ingress, all created automatically from a single command.
Three ways to deploy
Deploy from an image or a Git repository. Add a webhook to automate updates from your existing CI workflow.
| Mechanism | When to use | Command |
|---|---|---|
| Container image | You build images in your own CI and just want Kipper to roll them out. | kip app deploy --name backend --image ghcr.io/acme/api:1.2.3 --port 3000 |
| Git source | You want Kipper to build the image in-cluster from a git repo every time you push. | kip app deploy --name backend --git https://github.com/acme/api --port 3000 |
| CI webhook | You want your existing CI to push deploys to Kipper without anyone running kip by hand. Works on top of either of the above. | See Webhooks |
The web console's Deploys tab shows all three as side-by-side cards, so you can see what's currently active and what's still available to wire up.
From a container image
kip app deploy --name shop --image ghcr.io/acme/shop:latest --port 3000 Deploying shop...
✔ Deployment created
✔ Service created
✔ Public URL: https://shop--203-0-113-10.kipper.runThe last line shows the app's public URL once its route is published. If the route name or hostname is taken or reserved, kip prints ✗ Public URL unavailable: with the reason; choose another app name or hostname. A pending or unconfirmed URL means you should try it again shortly. Route publication is separate from app health; use kip app list to check whether the app is ready.
What this creates
Behind the scenes, Kipper creates an App Custom Resource (kipper.run/v1alpha1). A reconciler then ensures the underlying Kubernetes resources exist:
- Deployment: runs your container with the specified number of replicas
- Service: internal load balancer that routes traffic to your pods
- Ingress: external hostname with automatic TLS via cert-manager
All three are owned by the App CR. Deleting the app cascades to all related resources automatically.
All flags
kip app deploy \
--name backend \
--image ghcr.io/acme/api:latest \
--port 3000 \
--replicas 2 \
--project staging \
--env LOG_LEVEL=info \
--env API_URL=https://api.example.com \
--secret API_KEY| Flag | Required | Default | Description |
|---|---|---|---|
--name | Yes | — | Application name |
--image | Yes | — | Container image to deploy |
--port | Yes | — | Port the application listens on |
--replicas | No | 1 | Number of pod replicas |
--project | No | default | Project namespace to deploy into |
--env | No | — | Environment variable (repeatable). For non-sensitive config only; credentials go in --secret |
--secret | No | — | Secret (repeatable). KEY=VALUE inline, or a bare KEY for a hidden prompt that stays out of shell history. See secrets |
--route | No | — | Path route group (e.g. blog/api/users) |
--profile | No | standard | Resource profile: lightweight, standard, compute-heavy, memory-heavy, jvm |
--cpu / --memory | No | — | Fixed CPU/memory size: sets request and limit to the same value and selects the custom profile |
--health and --health-* | No | automatic | How Kipper checks whether a pod is ready for traffic. See Health checks and rollouts |
Secrets passed at deploy time are written before the app starts, so the first pod boot already sees them. A key set via --secret behaves exactly like one set with kip app secret set afterwards: masked in the console and CLI listings, kept out of kip export, with the previous value retained for kip app secret rollback. Passing the same key through both --env and --secret fails the deploy.
Use --profile jvm for Java, Spring and other runtimes with high startup CPU demand. It allows bursts of CPU during startup without reserving a full core permanently. At deploy time, choose either --profile or explicit --cpu/--memory values, which select the custom profile.
Explicit values set a fixed size. Switching to a named profile later preserves those values, which take precedence over the profile defaults. Use kip app update <app> --tuning auto to return CPU and memory to automatic sizing. See Resource Management for fixed sizes, ranges and profiles.
From a Git repository
Deploy directly from source code. Kipper clones your repo, builds a container image using your Dockerfile, pushes it to the internal registry, and deploys.
kip app deploy --name backend --git https://github.com/acme/api.git --port 3000 Deploying backend...
✔ Deployment created
✔ Service created
✔ Git source configured: https://github.com/acme/api.git (main)
Configure a webhook or run 'kip app rebuild backend' to trigger the first buildTriggering builds
Manual rebuild:
kip app rebuild backend --project blog --environment testAutomatic builds via webhook:
Configure your Git provider to send push events to the webhook URL. Kipper validates the token and triggers a build automatically.
Streaming build logs:
kip app build-logs backend --project blog --environment testHow it works
- A webhook or manual
kip app rebuildtriggers a build - Kipper creates a Kubernetes Job with two containers:
- clone: fetches your repo (single branch, depth 1)
- build: Kaniko builds the Dockerfile and pushes the image
- On success, the App CR's image is updated to the new Zot registry tag
- The App reconciler rolls out the new Deployment
Git deploy flags
| Flag | Required | Default | Description |
|---|---|---|---|
--git | Yes | — | Git repository URL |
--branch | No | main | Branch to build from |
--port | Yes | — | Port the application listens on |
--project | No | default | Project namespace |
--environment | No | — | Target environment |
--build-memory | No | 2Gi | Memory limit for the in-cluster build |
--build-cpu | No | 2 | CPU limit for the in-cluster build |
Builds that need more memory
Kipper builds your image in-cluster and gives each build 2Gi of memory by default. That covers most apps. Some builds need more: a server-rendered frontend build (Nuxt, Next, a large webpack or Vite bundle) runs the whole thing in one Node process and can use 4Gi or more. When a build runs out of memory it fails and Kipper says so plainly, with the OOM in the build message.
Give that app's build more room when you deploy it:
kip app deploy --name website \
--git https://github.com/acme/website.git \
--build-memory 6Gi \
--port 3000 \
--project acme --environment prodThe setting sticks to the app, so redeploys and rebuilds keep it. In a kipper.yaml it lives under the git source:
git:
url: https://github.com/acme/website.git
branch: main
buildResources:
memory: 6Gi
cpu: "2"To raise the default for every build on a cluster instead of per app, set BUILD_MEMORY_LIMIT (and optionally BUILD_CPU_LIMIT) on the console-api. A per-app setting always wins over the cluster default.
Private repositories
For private repos, pass your Git access token when deploying:
kip app deploy --name backend \
--git https://github.com/acme/private-api.git \
--git-token ghp_xxxxxxxxxxxx \
--port 3000 \
--project blog \
--environment test Deploying backend...
✔ Git credentials stored
✔ Deployment created
✔ Service created
✔ Git source configured: https://github.com/acme/private-api.git (main)The token is stored as a Kubernetes Secret named after the token and the repository host it is for, so rotating it writes a new one and the app moves onto it in a single step. Kipper removes the one it moved off. At build time git receives the token through a credential helper bound to the repository's host, so it never appears in the App CR, the clone URL, or the built image.
| Flag | Description |
|---|---|
--git-token | Personal access token for HTTPS clone (GitHub PAT, GitLab PAT, etc.) |
TIP
For GitLab, create a token with read_repository scope. For GitHub, a fine-grained PAT with Contents: Read is sufficient.
Registry credentials
Registry credentials let apps and functions run images from a private registry, for example ghcr.io or a company-internal one. In the web console open Settings → Container Registries and add the login:
| Field | Value |
|---|---|
| Server | ghcr.io |
| Username | your registry username |
| Password / Token | an access token for that registry |
The credential is stored once in kipper-system. Each credential carries an allow-list of projects, and a fresh credential starts with an empty list, so grant the projects that may use it:
kip registry add --server ghcr.io --allow-project acmeWhen a workload in an allowed project uses an image from that registry, Kipper stages a pull secret in the workload's namespace, scoped to that single registry, and removes it again when the image stops needing it. Workloads in other projects pull anonymously.
Builds are separate. The build container runs your Dockerfile's RUN steps, so registry credentials stay out of it, and base images in a FROM line are pulled anonymously. Docker Hub rate-limits anonymous pulls per IP, and every build on the cluster shares one egress IP, so a busy cluster can see builds fail with toomanyrequests until the window resets. Private base images are unsupported at build time. Publish shared base images to a registry the build can reach anonymously.
Build status
The Source tab in the web console shows the current build status, commit SHA, timestamps, and error messages. You can also trigger rebuilds and cancel active builds from there.
Build states are Pending, Building, Succeeded, Failed, and Discarded. A Discarded build used source settings that no longer match the app. This can happen when you edit the repository, branch, Dockerfile, build context, or build arguments during a build. Source edits alone do not trigger a build; start a new build to deploy the current source.
Moving an app off git
Detach the Git source when you want an external pipeline to manage the app's image. Otherwise, a later Kipper build can replace that image:
kip app git remove checkout --project shop --environment production ✔ checkout no longer builds from git
It keeps running the image it has. Deploy a new one with
'kip app update checkout --image <image>' or from your pipeline.The app keeps running the image it has. The stored access token and the last build's status go with the source. The Git source card in the console has a Remove button that does the same thing.
See the Source tab in the web console for a visual overview.
Routing
See Route groups to serve several apps under one hostname.
Route names
Kipper checks route names before creating a route for an app or service UI. If a name conflicts with another app or service, choose a different name. Changing only the port does not avoid the check.
A conflict can occur even when app names differ. For example, app prod-web in namespace team conflicts with app web in namespace team-prod. Consecutive dashes count as one, so prod--web and prod-web also conflict. If conflicting routes use the same port, requests can reach the wrong app or service.
The route status explains what to do:
RouteNameTaken: the new route was refused. Choose a different app or service name. Kipper retries every 10 minutes if you keep the name.RouteNameReservedForPlatform: the name is reserved for a Kipper component, even if it is not installed. Choose a different name. For example, appsystem-console-apiin namespacekipperconflicts with the console API. The console rejects this name when creating an app;kip applycan create the app, but its route is refused.RouteNameShared: an existing app route remains published, but its request figures cannot be separated from another app or service. Use distinct names to avoid requests reaching the wrong destination. See the upgrade rules.RouteNamePending: the app route is waiting for a name check and retries automatically. This can happen during an upgrade or restart. If you changed its host, the old route is removed while the new route waits. If the console asks you to retry app creation, try again once the checks finish.
An app or service keeps its route-name reservation while it exists, even with its route turned off. Choose a different name to avoid waiting for the reservation to be released.
Scaling
# Scale up
kip app scale backend --replicas 3
# Scale down
kip app scale backend --replicas 1To take an app out of service temporarily, stop it. This preserves its replica count and autoscaling settings, records who stopped it and why, and shows visitors a "this app is stopped" page.
The READY column in kip app list shows progress during scaling (e.g. 2/3 means 2 of 3 replicas are healthy). Kubernetes distributes traffic across all healthy replicas automatically.
In the web console, set Desired in the app's Scale tab. See In the console.
An app with a minimum and maximum keeps its count between them, and a count outside them is refused. See the minimum and maximum always hold.
Autoscaling
Autoscaling changes the number of pods an app runs, based on how much of its CPU or memory request the pods use. Kipper adds pods on the cluster's existing nodes. When the nodes are full, a new pod waits to be scheduled and the app shows a waiting rollout until room is made.
An app's capacity works like an AWS Auto Scaling group. Where a group adds and removes machines, Kipper adds and removes pods on the nodes the cluster already has:
| Auto Scaling group | Kipper | What it means |
|---|---|---|
| Desired capacity | Desired (replicas) | The number of pods that should run. You set it while the app has no scaling policy. While target tracking is on, the autoscaler sets the Deployment's desired count; the App's stored replica count is separate |
| Minimum capacity | Minimum (minReplicas) | The lowest desired count while the app is active, with or without a policy |
| Maximum capacity | Maximum (maxReplicas) | The highest desired count. Target tracking needs one |
| Target tracking scaling policy | CPU and memory targets (cpuTarget, memoryTarget) | The average use, as a percentage of each pod's request, that the autoscaler aims for. A target of 0 is unused |
| Current and InService instances | Current, as running and ready pods | Pods that exist, and pods that pass their health check |
| Activity history | Scaling activity | Recent scale changes from the autoscaler's events and Kipper's scale log. Both expire, so the list can be incomplete |
In the console
The app's Scale tab has a Capacity panel. Its top row shows Desired, Minimum and Maximum side by side, with Current beside them as running and ready pods.
Under Scaling policy, choose None (fixed count) to run the desired count you enter, or Target tracking to let the autoscaler set it. Target tracking adds a CPU target (% of request) and a Memory target (% of request), each showing the current use once the policy is saved. While target tracking is on, Desired is read only and shows the autoscaler's count followed by "(set by autoscaling)".
Minimum and Maximum can be edited under either policy. With no scaling policy, leave both empty to remove the bounds. Target tracking requires a maximum; an empty minimum means 1. The panel checks the values before anything is saved. The minimum must not exceed the maximum, a minimum needs a maximum, target tracking needs a maximum and at least one target above 0, and a desired count you enter must lie within the bounds. A desired count of 0 is refused with or without bounds when explicitly entered; use Stop app instead. A desired count above the maximum offers Raise maximum to that count, which changes the maximum in the form so that one save carries both. Choosing target tracking fills in missing bounds as 1 to 5 and sets CPU to 70 when both targets are 0.
Save capacity writes the change in one request on the current API, and Cancel restores the loaded values. When a save with no scaling policy would move the desired count into new bounds, the panel says so before you save. A target tracking save moves the stored count into the bounds on the server, and a notification names the move once the save is done. The autoscaler then sets the running count within the same bounds.
To switch autoscaling off, choose None (fixed count) and click Save capacity. The app keeps the Deployment's desired count, adjusted to fit the bounds. A stopped app uses its stored count. The minimum and maximum stay in place. To remove the bounds as well, clear both fields before saving.
Badges next to the heading show the autoscaler's state: At maximum, At minimum, Waiting for metrics, Not scaling at 0, Quota blocks new pods and Stopped, applies on start.
Whenever the App's AutoscalingReady condition is False, a badge shows that too, whether autoscaling is on or off: Policy invalid for an invalid block, Autoscaler not applied or Autoscaler not removed when the autoscaler could not be written or deleted, Desired outside bounds when the stored count lies outside the bounds, and Autoscaling not ready with the reason for anything else. Hover over a badge for details.
The counts, badges and activity refresh every 15 seconds while the Scale tab is open, and the refresh button reads them immediately. A refresh leaves values you are editing alone, and when one fails the panel keeps the last values and says so. When an app with a CPU target sits at its maximum, the panel suggests raising the maximum or the CPU request. Under Scaling activity the panel lists recent scale changes and the last scale time, with a reminder that the list can be incomplete. While new pods start, the autoscaler briefly has no metrics for them, and the panel shows those events as one line, "Metrics for new pods are not available yet; the autoscaler waits for them", with the autoscaler's own messages on hover. Entries from Kipper and the autoscaler with matching pod counts and nearby timestamps are combined into one line. It uses Kipper's description and the autoscaler's timestamp; hover for the autoscaler's full message. An app held at its maximum, or one whose AutoscalingReady condition turns False, also raises a warning alert.
Someone who can view the app but not change it sees the values without the controls.
During an upgrade the console can briefly talk to an older console-api. A save that switches autoscaling off and changes the desired count then goes out as two requests, and the panel warns that the second request can fail after the first succeeds. That older console-api stores bounds only together with target tracking, so the panel asks you to save the desired count on its own or turn target tracking on.
Traffic and scaling
In the Scale tab, Traffic and scaling compares traffic, resource use and desired pods over the last hour, 6 hours, 24 hours or 3 days. Your browser remembers the selected range. Three charts share a time axis:
- Requests per minute, grouped by response status: 2xx, 3xx, 4xx, abandoned and 5xx. The thin line estimates peak request rates. Abandoned requests have status 499: the client disconnected before receiving a response.
- CPU or memory use, as a percentage of the resource request. The dashed line shows the autoscaling target; the faint band extends 10% above it. The lighter CPU line shows estimated peaks. Select CPU or Memory to switch. Memory is selected initially when it is the only autoscaling target.
- Desired pods, with a faint band between the autoscaling minimum and maximum. This is the count the app should run, not the number of ready pods.
Point at a vertical marker to read about a change in the desired count:
- Solid, unlabelled: a matching autoscaler event records the change.
- Dashed: autoscaling was on before and after the change, but no matching autoscaler event is available. Older events may have expired.
- Solid, labelled: the count moved to or from zero, or matched a nearby change to the autoscaling minimum or maximum.
- Dotted: there is not enough autoscaling data to explain the change.
For the 20 most recent dashed or unlabelled solid markers, available measurements cover the 2 minutes before an increase or 5 minutes before a decrease. They include CPU and memory peaks and averages for the configured targets, plus estimated request counts. Older measurements may no longer be available. Point elsewhere on a chart to read the values at that time.
While autoscaling is on and the app is not stopped, a guide estimates when pods may be added, up to the configured maximum. For example, a 70% CPU target gives an estimated threshold of 77% of the CPU request. This assumes the default 10% tolerance; a cluster can use a different value.
The charts refresh every minute while the Scale tab is open and the page is visible. Monitoring must be enabled on the cluster. If figures cannot be loaded, the panel says so.
When reading the charts:
- CPU and request rates are averages. Peak lines help show brief bursts; the autoscaler measures usage separately and may see spikes the charts miss. Memory and pod counts show values measured at each point.
- Changes are detected from samples spaced 30 seconds apart, so their times are approximate. Missing data can hide changes.
- Request figures cover traffic through the app's route. They exclude direct traffic between apps and requests rejected before reaching the app's routing service, for example by authentication or rate limiting.
- Gaps mean figures are unavailable, not zero. They can appear after a route is created or after an upgrade. The newest figures can take another refresh to appear.
- If route names conflict, request figures may be unavailable because they cannot be separated from another app or service. Use a different app name.
Compare pod changes with traffic and resource use to assess demand. If CPU use stays above target at the maximum pod count, review the maximum and CPU request; see the at-maximum alert. Check the traffic source if 4xx or abandoned requests rise. A route rate limit can help control unwanted traffic.
In kipper.yaml
apps:
backend:
image: registry.example.com/api:v2
port: 8080
autoscale:
enabled: true
minReplicas: 2
maxReplicas: 10
cpuTarget: 70kip apply checks the block before it writes anything:
- With
enabled: true,maxReplicasis required and at least one target must be above 0. Targets can exceed 100%. minReplicascan be left out, which means 1. An explicit 0 or a negative value is refused, as is a minimum above the maximum.- A block with
enabled: falseand amaxReplicaskeeps its bounds while autoscaling is off.enabled: falsewithout amaxReplicassets no bounds, so aminReplicasabove 1 needs amaxReplicasbeside it and is otherwise refused withset maxReplicas with minReplicas, or leave both out to remove the bounds. - A
replicasfield must lie within the bounds. While autoscaling is on it is stored as the count for when autoscaling is switched off. - Leaving
replicasout of a manifest with bounds keeps the App's stored replica count when it lies within them, and otherwise usesminReplicas. A new app starts atminReplicas. While autoscaling is on, the running count can differ from the stored one, so turning autoscaling off in the manifest can change the number of pods with no replica change inkip diff, as Switching autoscaling off explains.
See Applying a manifest for how apply treats the rest of the spec.
From the CLI
# Scale between 2 and 10 replicas, targeting 70% CPU
kip app autoscale backend --min 2 --max 10 --cpu 70
# Change one setting; the others keep their stored values
kip app autoscale backend --max 15
# Show the desired count, the bounds, ready pods and current usage
kip app autoscale backend --status
# Switch autoscaling off, keeping the running count and the bounds
kip app autoscale backend --off
# Remove the bounds once autoscaling is off
kip app autoscale backend --remove Autoscaling: on (CPU 70%)
Desired: 4 (set by autoscaling) Min: 2 Max: 10
Ready: 4
cpu: target 70%, current 58%When the App's AutoscalingReady condition is not True, --status adds a line such as ⚠ Autoscaling is not ready (InvalidPolicy): the autoscaling policy is not applied: minReplicas (6) must not exceed maxReplicas (5) with the reason and message the cluster reports.
Flags you leave out keep their stored values. Missing bounds default to a minimum of 1 and a maximum of 5. With neither target set, CPU defaults to 70%. --cpu 0 or --memory 0 removes that target, and with no target left the CPU target of 70% applies. kip app autoscale with any setting flag switches autoscaling on.
--off and --remove ignore --min, --max, --cpu and --memory and print a notice naming the flags they ignored. To change a setting and switch off, run the change first and --off after it, for example kip app autoscale backend --max 6 and then kip app autoscale backend --off.
The minimum and maximum always hold
Kipper checks replica changes against the app's bounds:
kip app scale,kip app deploy --replicas, the console's Desired field andkip applyrefuse a count outside the bounds, and the message names them. The way to run no pods is stopping the app, so a count of 0 is refused too.- Changing the bounds with
kip app autoscaleor in the console moves the stored count into them in the same write. A minimum above the count raises it, and a maximum below it lowers it. The CLI prints the move. The console shows it before you save when no scaling policy is chosen, and in a notification after any save that moved the stored count. Inkipper.yamla declaredreplicasoutside the new bounds is refused instead, so change both together. - Switching autoscaling off keeps the bounds. Remove them with
kip app autoscale backend --remove, by clearing Minimum and Maximum in the console with no scaling policy, or by deleting theautoscaleblock fromkipper.yamland applying with--force.--removerefuses while autoscaling is on;--off --removedoes both in one go.
While autoscaling is on, kip app scale refuses any count, because the autoscaler would overwrite it. kip app deploy --replicas stores the count and prints a note that it has no effect until autoscaling is off.
Switching autoscaling off
kip app autoscale backend --off and choosing None (fixed count) in the console preserve the Deployment's desired count, adjusted to fit the bounds. The CLI reports any adjustment. A stopped app uses its stored restart count. If the Deployment's count cannot be read, the stored count is used and a warning explains why. The console also adjusts these fallback counts to fit the bounds.
kip apply with enabled: false is different, because the manifest is the declared state. The app runs the manifest's replicas, whatever the autoscaler was running. When the manifest leaves replicas out, the app runs its stored count if that lies within the bounds and minReplicas otherwise, the same rule as In kipper.yaml. To keep the autoscaler's count, switch autoscaling off with kip app autoscale <app> --off first and fold the result into the manifest with kip export.
How automatic sizing steps back
The autoscaler measures use as a percentage of each pod's request. Raising a tracked resource's request lowers its measured utilization, which can prompt the autoscaler to remove pods. To keep these signals stable, automatic sizing follows these rules:
- CPU target: automatic sizing keeps CPU requests and limits unchanged, including when pods reach their CPU limits. The autoscaler handles CPU pressure by adjusting the replica count. To give each pod more CPU, set the CPU request yourself.
- Memory target: automatic sizing keeps memory requests and limits unchanged during routine sizing. It can still raise memory after an out-of-memory kill, since adding pods cannot resolve an individual pod's memory shortage.
A metric without a target is sized as before. Your own CPU and memory values, the OOM cap and project quotas apply as usual. In auto mode, the note at the top of the Scale tab says which resources automatic sizing still adjusts for the app, and mentions that scale-down is paused only while the app runs a single replica.
When settings do not add up
Older apps can contain settings that violate these rules. Kipper reports invalid settings and preserves the existing Deployment's desired count instead of applying them. With an invalid enabled policy, an existing autoscaler keeps its previous settings and can still change that count. The App's AutoscalingReady condition gives the reason (InvalidPolicy or ReplicasOutsideBounds), kip app autoscale backend --status and the console's badges show it, and kip upgrade --check lists every such app with the command that fixes it.
The cluster's API server validates the autoscaling block on writes from any client and names the violated rule when refusing a change. This checks the block itself; Kipper's commands and controllers also check replica counts against its bounds. A block stored before the rules existed stays as it is, and a write that leaves it untouched, such as an image deploy, still goes through. A write that changes the block has to leave it valid, and switching autoscaling off changes it. On an app whose stored minimum is above its maximum, kip app autoscale backend --off is therefore refused with minReplicas must not exceed maxReplicas. Fix the bounds first with kip app autoscale backend --min 2 --max 5 and then switch off, or correct Minimum and Maximum in the console and choose None (fixed count) in the same save.
An app stored with autoscaling on, a minimum above 1 and no maximum is caught the same way. Switching off would leave a minimum without a maximum, so kip app autoscale backend --off is refused with set maxReplicas with minReplicas, or leave both out to remove the bounds, and --remove asks you to switch off first. Set a maximum no lower than the stored minimum, such as kip app autoscale backend --max 5 for a minimum of up to 5, then switch off. When the cluster refuses --off like this, kip prints the command that fixes the block.
An autoscaled app whose pods were taken to 0 without a stop is started at its minimum, because the autoscaler cannot scale up from zero.
Resource requests required
The autoscaler needs a CPU request for a CPU target and a memory request for a memory target. Kipper sets both from the app's resource profile. If you set resources yourself, keep the requests.
App connections and internal paths
See Routing & App Links for app connections, route groups, and internal path protection.
Managing apps
List all apps
kip app list --project staging NAME STATUS IMAGE READY
backend running ghcr.io/acme/api:latest 2/2
frontend running ghcr.io/acme/frontend:latest 1/1In the web console, every app row on the Projects screen shows the app's public URL with an open-in-new-tab link, so you can reach a running app without opening its detail panel.
Inspect one app
kip app info backend --project stagingShows the app's status, scaling settings, and last autoscaling time when available. It also shows CPU and memory values from the pod template and explains how Kipper sizes each resource. The Conditions section includes reasons and messages for rollout progress, warnings, and failures. See kip app info.
View logs
kip app logs backend
kip app logs backend --tail 50
kip app logs backend -fPrints the last 500 log lines from each current pod and exits. Use --tail N to change the number of lines per pod. Add -f (--follow) to stream new lines until all streams end or you press Ctrl+C.
By default, logs are grouped by pod, oldest pod first. In follow mode, lines from different pods interleave as they arrive. With multiple pods, each line starts with [pod/<pod>/<container>] to identify its source. Follow mode reads only the pods found when the command starts.
If a pod's logs cannot be opened or read, the command reports the pod name and error on stderr and continues reading the other pods.
Update an app image
kip app update backend --image ghcr.io/acme/api:v2.1.0Changes the container image and triggers a rolling update. Use this when you have a new version of your application to deploy.
For apps within a project environment:
kip app update backend --image ghcr.io/acme/api:v2.1.0 --project blog --environment testRollback history
Kipper keeps the 3 most recent versions of each deployment. Kubernetes can roll back to any of these if a new version fails to start. The previous 2 versions are retained automatically, and older ones are cleaned up to save resources.
Restart an app
kip app restart backendTriggers a rolling restart, which replaces the pods as described in How pods are replaced. With a health check in place, a new pod counts as ready once your app passes the check. Without one, Kubernetes counts a pod as ready as soon as its container starts. Useful when you need to pick up new environment variables or pull a fresh :latest image.
The command returns once the restart is requested. Add --wait to wait until all replacement pods are ready and all pods from before this restart are gone:
kip app restart backend --waitThe wait lasts up to 15 minutes and fails earlier if Kubernetes reports that the rollout has stalled. Stall and timeout errors include pod counts once the controller has applied the restart. Run kip app info backend to check the app's status and rollout details.
Stopping and starting an app
kip app stop backend --reason "not needed until the next campaign"
kip app start backendIn the console, click Stop in the app panel's header to enter an optional reason and see what will keep running.
Stopping an app shuts down its pods and releases their CPU and memory. It preserves the app's configuration, replica count, autoscaling settings and bounds, route and deployment history. Volumes and their data remain available. Bound services, databases, functions and jobs keep running.
While an app is stopped:
- Its route returns a "this app is stopped" page with HTTP status 503. Requests with
Accept: application/jsonreceive{"code":"app_stopped","message":"This app is stopped."}. Basic auth, API-key checks and internal-path restrictions still apply. The page shows the host, without the stop reason or operator's identity. kip app listshowsstopped, followed by the recorded time, operator and reason. The console shows these details in the app panel with a Start button.- Crash-loop and stuck-rollout alerts are suppressed, and automatic sizing is paused.
- Image changes, rollbacks, replica counts and autoscaling changes are saved for the next start. Autoscaled apps continue to reject manual replica counts, and a count outside the app's bounds is refused. Use
kip app startto resume a stopped app;kip app restartrejects it.
kip app start uses the current configured replica count, or the autoscaling minimum for an autoscaled app. A configured count outside the app's bounds starts at the nearest bound instead. When the autoscaling settings are invalid, the start uses the configured count. Automatic sizing recommendations remain paused during the start; explicit resource settings still apply. New pods take traffic once they pass their health check. kip app list shows rollout progress until they are ready.
Repeating kip app stop preserves the original operator and timestamp. It updates the reason only when you provide --reason. For an app scaled to zero without a stop record and without autoscaling, use kip app scale to raise its replica count. An autoscaled app in that state starts at its minimum on its own. Without autoscaling or bounds, starting a stopped app whose configured count is zero also leaves it at zero.
Older App schemas may discard the stop field even when the write succeeds. kip app stop, kip apply and the stop API report failure if the field was dropped. Upgrade Kipper before retrying.
kip export includes the stop in the manifest. Applying a manifest that omits it would start the app, so kip apply requires --force to remove it. See the stopped block.
Downgrading
Older Kipper versions do not honor stop records. A downgrade can resume apps without autoscaling and discard their stop records, while autoscaled apps may remain at zero replicas. Review stopped apps before downgrading.
Delete an app
kip app delete backendRemoves the Deployment, Service, Ingress, and all associated Secrets.
Health checks and rollouts
Kipper uses rollouts to deploy apps and apply image, resource, or readiness changes. A health check determines when a pod is ready to receive traffic. How many old pods remain available during the rollout depends on the replica count; see How pods are replaced.
A failed readiness check keeps traffic away from the pod and can delay the rollout. It does not restart the container.
What Kipper checks on its own
If you omit the check, Kipper infers one during a rollout, such as an image update, restart, or resource change. It tries a TCP connection to the app port on up to three running pods, prioritizing ready pods and older app containers.
- A ready pod accepts a connection: new pods get a TCP check on the app port. Kipper keeps this choice for later rollouts.
- No ready pod accepts, and an app container running for at least 10 minutes refuses a connection: Kipper adds no app check and tries again on the next rollout.
- Results are inconclusive, such as timeouts or refusals only from younger containers: the rollout proceeds without an app check, and Kipper tries again next time.
A background worker that never listens on a port can therefore deploy without a check.
A refused connection can also mean the app listens on a different port from its declared port. For routed apps and apps targeted by a link, Kipper checks the port every five minutes while automatic readiness has selected no check or is still pending. Stopped apps and build placeholders are excluded.
The PortListening condition is true if any sampled pod accepts a connection. If none accepts directly, Kipper also asks each sampled instance proxy to connect to its app on localhost, which reaches listeners bound to 127.0.0.1. A successful proxy connection also makes the condition true. Otherwise, a refusal makes it false once the app container has run for at least 10 minutes; for pods with an instance proxy, that refusal must come from the proxy's check. Errors, unknown replies, and replies without the check's response header are inconclusive and preserve the previous condition if no other sample is conclusive.
kip app info shows the condition. When it is false, the console shows "Port not listening", and kip app links flags links to the app. For a worker that serves no traffic, apply --health none to disable these checks.
A new app has no running pods to inspect, so its first deploy has no automatic app check. A later rollout gets one if a ready pod accepts connections on the app port.
Automatic and declared readiness checks connect to the pod's IP address. Make the checked endpoint listen on 0.0.0.0 so these checks can reach it. An app listening only on 127.0.0.1 can receive traffic through the instance proxy and use a separate health endpoint with --health-port.
Declaring a check
Declare an HTTP check when an open port does not mean the app is ready, for example when it still needs to warm caches or connect to a database. A declared check also lets you allow more startup time.
kip app update backend --health-path /actuator/health/readiness --health-startup-timeout 600
kip app update queue-worker --health none
kip app update backend --health auto # remove the declared check and let Kipper decide| Type | What a pod must do |
|---|---|
http | Respond to a GET on the configured path with HTTP 200-399 |
tcp | Accept a TCP connection on the configured port |
none | No app readiness check; intended for apps that serve no traffic |
| Flag | kipper.yaml field | Default | What it sets |
|---|---|---|---|
--health | type | automatic | http, tcp or none. auto removes a declared check |
--health-path | path | — | HTTP check path: starts with /, contains no whitespace, and is at most 1024 characters. Implies http when --health is omitted |
--health-port | port | the app port | Port to check, such as a separate management port. Cannot be the instance proxy's port (app port + 10000) |
--health-startup-timeout | startupTimeoutSeconds | 300 | Time from pod creation to readiness before the pod is reported as stuck, including image pulls: 10-3600 seconds |
--health-timeout | timeoutSeconds | 2 | Timeout for each check: 1-60 seconds |
The startup timeout includes scheduling and image pulls. Kipper can report specific scheduling, image, configuration, or repeated-crash errors before it expires.
These flags work with both kip app deploy and kip app update. Updates merge into the existing check, so --health-timeout 5 preserves its path. Switching types clears unsupported settings: none accepts a startup timeout but no path, port, or check timeout. Changes to the readiness probe trigger a rollout. Changing only the startup timeout does not restart pods.
Older clusters may silently discard health settings. The CLI and console verify that the cluster stored a declared check and ask you to run kip upgrade if it was dropped. In a manifest, put the settings under health:; see The health block.
Kipper configures checks every 5 seconds. Three consecutive failures mark a pod unready and remove it from traffic; one successful check makes it ready again. Detection time depends on the check timeout.
Git apps run a placeholder page until their first build is deployed. While it runs, any declared HTTP or TCP check uses a TCP check on the app port.
In the console, open the app's Settings tab and use Health check. Choose Automatic, Port check (TCP), HTTP path, or None, then click Save health check. The status line shows the current check or whether a saved change is still being applied.
How pods are replaced
New apps use Kubernetes' default rolling-update strategy. With one to three replicas, Kubernetes adds one extra pod at a time and waits for a replacement to become ready before stopping an old pod. With four or more replicas, it may also stop up to a quarter of the pods before replacements are ready. This can free capacity for the rollout. Existing Deployments retain their configured strategy.
With a health check, a pod becomes ready after passing it. Without an app check, the app container counts as ready once it starts; the instance proxy has its own check.
A single-replica app temporarily needs capacity for a second pod. If no node has room, the rollout waits while the existing pod continues serving, provided it remains healthy.
On supported clusters running Kubernetes 1.30 or newer, each container waits 10 seconds before receiving the termination signal. This gives routing changes time to propagate. The pod has a total shutdown grace period of 40 seconds, including that delay. The instance proxy waits for active HTTP requests to finish within the remaining time.
When a rollout waits
The App's RolloutComplete condition reports rollout progress separately from pod health. A rollout is complete when all replicas have been updated and are available, or Kubernetes has recorded the current revision as available with the expected replica counts. A pod that crashes afterwards affects app health but does not reopen the rollout. A scale-up or replacement pod can still show a waiting rollout if it cannot be scheduled or created, or encounters a startup error before any of its containers have run.
| Reason | What it means | What to do |
|---|---|---|
InProgress | Pods are being replaced or scaled | Wait for the rollout to finish |
Unschedulable | A new pod cannot be scheduled; the message includes the scheduler's reason | Follow the message. For insufficient CPU or memory, lower the request or add capacity |
QuotaExceeded | The project quota blocks new pods | Lower the request or raise the project quota |
PodsRefused | The cluster rejected a new pod, for example because of an admission policy | Resolve the error in the message |
PodsNotStarting | A pod has an image, configuration, or crash-loop error, or has not started running within the startup timeout. The message includes container details when available, otherwise the pod phase | Inspect the pod status, events, and logs. Fix the reported error; for an out-of-memory failure, review the memory limit |
NotBecomingReady | A pod is running but remains unready after the startup timeout, measured from pod creation | Inspect its logs and check settings. For a slow starter, increase --health-startup-timeout; for an inferred check, use --health tcp --health-startup-timeout 600 |
DeadlineExceeded | Kubernetes reported no progress within the deadline: at least 10 minutes, or the startup timeout plus 5 minutes | Inspect the message and pod logs |
NotApplied | Kipper could not apply the latest change | Resolve the reported error; the App's other conditions may provide more detail |
kip app list prints a note for each app still rolling out:
! backend is still rolling out: A new pod cannot be placed: 0/1 nodes are available: 1 Insufficient memory. Any healthy current pods continue serving. Lower the CPU or memory request, or add capacity.In the console, the Apps list shows Rolling out or Rollout waiting, with details on hover. The app panel shows an amber banner when a pod cannot be scheduled. Kipper records a RolloutWaitingForCapacity warning event when this state begins. A rollout that exceeds its progress deadline also raises an alert with the available diagnostic details.
Automatic resource sizing normally waits during a rollout. See Resource Management for the exceptions.
Existing apps after an upgrade
These platform changes do not restart existing apps during an upgrade. Apps adopt the new readiness and shutdown settings on their next image change, restart, or other pod-template change. Run kip app restart <app> to apply them immediately.
If Kipper selects a check, the first rollout uses it for the new pods. Pods created before the upgrade lack the shutdown delay; their replacements use it on supported clusters. See Upgrade scope for downgrade behavior.
These app rollout settings do not apply to functions, jobs, or services.
Browsing files
See Browsing Files for uploading, downloading, and editing files inside running containers.
AI diagnostics
See Observability for AI-powered log analysis and diagnostics.
Instance ID header
When you're running multiple replicas of an app, it's useful to know which pod handled a particular request. Kipper can add an X-Instance-ID response header to every HTTP response, identifying the pod that served it.
This is enabled by default for all apps with a route. You can toggle it off in the app's Settings tab under "Instance ID header".
How it works
Kipper injects a lightweight reverse proxy sidecar container into each pod. The sidecar sits in front of your app and adds the header transparently. Your app doesn't need any code changes.
The request flow looks like this:
Client → Traefik → Service:8080 → Sidecar(:18080) → Your app(:8080)The sidecar listens on an offset port (your app's port + 10000). The Kubernetes Service routes traffic to the sidecar via targetPort, and the sidecar forwards it to your app on localhost. Your app keeps listening on its original port.
The sidecar also answers Kipper's port check by testing a connection to your app. Older sidecars forward this check to the app, so it may receive a KIPPER-UPSTREAM-CHECK / request every five minutes until its pod is replaced. Requests with an X-Forwarded-For header, such as those from Traefik, follow the usual proxy path.
The sidecar has its own TCP readiness check. A pod receives traffic when both containers are ready; without an app check, the app container counts as ready once it starts. On shutdown, the sidecar waits for active HTTP requests to finish within the pod's shutdown grace period.
The header value is a short hash of the pod name (8 hex characters). It doesn't reveal the full pod name or any infrastructure details. For example:
X-Instance-ID: f1582f7cYou can match this ID to a specific pod in the live logs viewer. The live logs tab lets you pick individual pods, so once you see which instance handled a failing request, you can jump straight to that pod's logs.
When to disable it
Most apps should leave this on. You might turn it off if:
- Your app already adds its own instance tracking header
- You want to avoid the ~5MB memory overhead of the sidecar container per pod
- Your security policy doesn't allow extra response headers