Preskoči na sadržaj

The .platform.yml Contract

Source of truth

The Jinja2 templates in hns-platform/roles/hns-service/templates/*.j2 (which consume these keys) and the real .platform.yml files in each service repo. Update this page when a template starts consuming a new key.

Every deployable service repo carries a .platform.yml at its root. It declares what the service needs (containers, ports, health checks, scaling, ingress); the platform supplies the infrastructure values (database host, credentials, domains) as Jinja2 variables. A service writes {{ database_url }} and never knows where PostgreSQL lives.

The service decides The platform decides
Container image, command, ports, health-check paths Server IPs, network topology, namespace
Resource requests/limits, scaling min/max Connection strings, credential values
Env variable names and {{ var }} references Env variable values (vault / Nebion)
Ingress host, post-deploy commands Image tag, pull policy, TLS, labels

Top level

deployments:   # required — list of Deployment + Service (+ HPA/Ingress) stanzas
  - name: ...
jobs:          # optional — one-shot batch Jobs
  - name: ...

YAML anchors (x-…:, e.g. x-backend-env) are ignored by the role — it reads only deployments and jobs. They exist purely to share blocks within the file (docker-compose-style extension fields).

Per-deployment keys

Key Renders to
name Deployment/Service/Ingress/HPA resource name; pod label app
build {context, dockerfile, args} — image is built from the repo and pushed to the internal registry. Omit for pre-built public images.
init_containers initContainers[]{name, image, command, volume_mounts}
containers containers[] (required, ≥1) — see below
scaling HPA — {min_replicas, max_replicas, target_cpu} (also sets replicas)
discovery when truthy, also renders a headless Service (for stateful peer discovery)
ingress Ingress + TLS — see TLS & ingress
post_deploy commands run after a successful rollout — [{container, command}]

The Deployment strategy is chosen automatically: Recreate if any container volume requests a size (PVC-backed, can't have two pods on one RWO volume), otherwise RollingUpdate with maxUnavailable: 0, maxSurge: 1 (zero-downtime). replicas = scaling.min_replicas (default 1).

Per-container keys

Key Notes
name Container name
image Image URI. The exact string build is substituted with registry/<deployment-name>:<image_tag>. A sidecar reusing another deployment's image must spell out the full {{ registry_endpoint }}/<name>:{{ image_tag }}.
command / args Override ENTRYPOINT / CMD. Use args when the image's ENTRYPOINT already names the binary (e.g. ntfy: args: ["serve"]).
port or ports Single port: 80, or ports: [{port, name?}, …] for multi-port (e.g. NATS).
env Map of NAME: value. Values are rendered with string \| to_json so backslashes/quotes (e.g. a CORS regex) survive. A value of { field_ref: metadata.namespace } becomes a valueFrom.fieldRef.
config [{source, mount, template?}] — file mounted from the repo via ConfigMap. source is a repo-relative path; the file lands at mount (subPath = its basename). template: true renders it as Jinja2 first.
volumes [{name, mount, size?|secret?}]size ⇒ PVC (<deployment>-<name>), secret ⇒ secret volume, neither ⇒ emptyDir.
probes {startup, readiness, liveness}, each {path, port, period_seconds?, failure_threshold?, initial_delay_seconds?} → HTTP-GET probes.
resources {requests: {memory, cpu}, limits: {memory, cpu?}}. Shorthand {memory, cpu} sets request = limit. Defaults: 256Mi / 100m.

Probe defaults if omitted: startup period 1s / failure 600; readiness initialDelay 5s / period 5s / failure 3; liveness initialDelay 30s / period 10s / failure 3.

Ingress sub-keys

Key Effect
host FQDN, usually name.{{ domain }}. Also auto-collected into the shared TLS cert.
path Route prefix (default /).
port Backend service port (default = first container's port).
websocket true adds a Traefik sticky-cookie annotation (stateful WS across replicas).
basic_auth htpasswd string (user:bcrypt-hash) → renders a Traefik basic-auth Secret + Middleware. Empty = open. Guards only the public host; ClusterIP traffic is unaffected.
internal_paths List of path prefixes (e.g. /api/v1/internal) that Traefik serves only in-cluster and returns 403 for from outside.

Jobs

jobs[] entries take {name, image, command, env, config, backoff_limit?, ttl?} (defaults: backoff_limit 3, ttl 86400s). Jobs do not support image: build — point them at a pre-built image.

Worked example

A trimmed two-container deployment (nginx + app) with a build, scaling, an internal-only path, and post-deploy migrations:

deployments:
  - name: my-service
    build:
      context: .
      dockerfile: Dockerfile
    containers:
      - name: nginx
        image: nginx:1.27-alpine
        port: 80
        config:
          - source: docker/nginx/k8s.conf
            mount: /etc/nginx/conf.d/default.conf
        probes:
          readiness: { path: /stub_status, port: 80 }
      - name: app
        image: build                      # → registry/my-service:<sha>-<ns>
        port: 9000
        command: ["app-server"]
        env:
          DATABASE_URL: "{{ database_url }}"   # value supplied by the platform
          K8S_NAMESPACE: { field_ref: metadata.namespace }
        resources:
          requests: { memory: 512Mi, cpu: 200m }
    scaling:
      min_replicas: 1
      max_replicas: 5
      target_cpu: 70
    ingress:
      host: "api.{{ domain }}"
      port: 80
      internal_paths:
        - /api/v1/internal
    post_deploy:
      - container: app
        command: "app-server migrate --no-interaction"

Last Updated: June 2026