Skip to content

DevOps portal — Applications

Apps → Deploy an app runs a container image in one of your team's namespaces. The portal writes teams/<team>/<namespace>/apps/<app>.yaml in kube-ops, and Flux deploys it once the merge request is merged. You need a namespace first.

The form, section by section:

Where

  • Owning team and Namespace: the app runs in that namespace, on its cluster.
  • Name: 2 to 32 lowercase letters, digits and hyphens, starting with a letter. It names the Deployment and Service (<app> inside the namespace). Team, namespace and name can't be changed later.

Image

  • Repository: start typing and the portal searches Harbor (core.harbor.jsm.systems/<project>/<image>). An image from another allowed registry is typed in full, with the registry host: docker.io/library/nginx, not nginx. The cluster accepts core.harbor.jsm.systems, docker.io and ghcr.io.
  • Tag: for a Harbor image, a list of its tags, newest push first. Otherwise type it. A tag can carry a digest: 1.4.2@sha256:….

Follow new tags

Harbor images can follow new tags: when CI pushes a matching tag, Flux opens a merge request in kube-ops that moves the app to it, within about 5 minutes of the push. Choose:

  • Newest build: tags matching a pattern, highest number wins. The default pattern is ^v(?P<ts>\d+)$ (tags v1, v2, …); change it to match your CI, keeping a named group for the number.
  • Semver range: the highest tag in the range wins.

The form shows the tag the rule would deploy right now (Would deploy … now), so you can check a rule before you submit it.

What happens to the merge request depends on the environment:

Env Image update merge request
dev, qa Merges by itself when its pipeline passes
prod Waits for a Maintainer to merge it

Getting semver ranges right:

  • Write the range out. For a project at 0.0.2, use >=0.0.2 <1.0.0. Avoid ^0.0.2: for 0.0.x versions a caret means exactly 0.0.2, so nothing new ever matches.
  • Pre-release tags are ignored (1.3.0-rc.1, 2.0.0-beta). Only release versions are picked.
  • Turn on tag immutability in Harbor for your project (a rule for every tag). The update writes the tag, not the image digest, so if a tag is pushed again with a different image, nothing notices.
  • Your CI only pushes tags. It doesn't touch kube-ops: build, tag (1.4.0, or v<build> for newest build) and push to Harbor. Tags that don't match the rule are ignored.

To stop following, untick Follow new tags with Change app; the tag stays where it is.

Run

Field Default Notes
Port 8080 1024 to 65535: containers run as non-root. The container port, Service port and health check port
Replicas 1 0 to 10
Size S S 50m CPU / 128Mi, M 250m / 512Mi, L 1 CPU / 2Gi, or Custom. CPU and memory are requests; memory is also the limit (no CPU limit)
Health check path none: a TCP check on the port An HTTP path such as /healthz, used for the liveness and readiness probes on the port

Pods run with a restricted security profile: non-root, no privilege escalation, all capabilities dropped.

Config

Environment variables are plain name and value pairs, stored in the kube-ops file, so never put secrets here.

Secrets come from OpenBao, from a vault that is connected to the namespace (Vaults → the vault → Connections). Each secret is an env var name plus where the value is:

  • Vault: a dropdown of your team's vaults connected to this namespace. If it's empty, connect a vault first.
  • Item: type it, with suggestions from the vault's items.
  • Key: type it, with suggestions. The portal reads the item's key names from OpenBao with your own access, names only, never values. If you can't read the item (for example a developer and a production vault), you get You can't read this item; type the key: type the key name and it works the same.

The app gets the values as a Kubernetes Secret through External Secrets. When a value changes in OpenBao, the app picks it up at the next refresh (within an hour) and restarts. Each env var name can be set once: not as both a variable and a secret.

See Secrets and, for database credentials, Requesting a database.

Expose

Reachable:

Choice Meaning
Not exposed No route. Other pods in the namespace can still call the Service
Internal only On the internal gateway: reachable inside the network and over the VPN
Public, through the web firewall On the internet, through the web firewall
Internal and public Both
  • Hostname: one of the namespace's hostnames, or Any name under <domain> when the namespace has a wildcard; then type the name. See Hostnames and domains.
  • Path: a path prefix, / by default.
  • Web firewall (public routes): Detect only (log, don't block), the default, or Block. Start in detect, check the WAF's events, then block. Once the app is public, its mode is changed with the web firewall's mode switch, not here.
  • Trust the internal certificate authority: mounts the internal CA bundle and sets SSL_CERT_FILE, for apps that call internal HTTPS services.

DNS and the certificate come automatically: see Hostnames and domains.

Sign-in

With a route, Require sign-in puts a Keycloak sign-in in front of the app at the gateway, so the app needs no OIDC code. Pick one of your team's confidential Keycloak clients in the same environment and a vault item for its secret; Store the client secret there copies it from Keycloak with your own access, without showing it. The client must allow the redirect URI the form shows (Add it to the client opens that change).

Signed-in requests reach the app with X-Auth-Email, X-Auth-Name and X-Auth-Subject. Who may sign in is decided by the client's roles. Served without sign-in lists exact paths that skip it, such as a web manifest and its icons. <path>/logout signs out.

After it's deployed

The app's drawer shows its status, image, route and pods.

Status Meaning
Deploying A change is open, or the new version isn't rolled out yet
Running Every replica is ready on the wanted image
Degraded Running, but fewer replicas are ready than wanted
Failed Not ready, with Flux's or Kubernetes's message
  • Deploy a tag changes only the tag.
  • Change app opens the form again and rewrites the whole file in the portal's layout: defaults are written out, and comments inside the file are dropped (the header stays).
  • Restart starts new pods and stops the old ones one at a time.
  • Logs shows the last 500 lines (or 15 minutes) of a pod, including the previous container after a crash.
  • An app has one open change at a time: wait for it to finish before the next.

A deploy waits up to 15 minutes for the rollout. If it doesn't finish, the change fails with the reason and the file stays as merged. To roll back, Deploy a tag with the previous tag; if the app follows new tags, turn that off first, or Flux moves it forward again.

Approvals

Change dev, qa prod
Deploy a new app A team owner A team owner, then platform
Publish it, move its public hostname, weaken its WAF, or give it a new secret A team owner A team owner, then platform
Anything else (tag, replicas, size, env vars, health check, internal route), delete A team owner A team owner

The team owner step is skipped when you're an owner yourself.

Limits

A portal app is one container, one route, HTTP. Anything else, such as sidecars, workers with several containers, CronJobs or non-HTTP services, goes as plain Kubernetes YAML in the namespace's manifests/ folder in kube-ops, within what the namespace's permissions and policies allow.