Skip to main content

App

Available

Deployed and supported. The fields and commands described here are stable within version v1alpha1.

apiVersiondeploy.stackngo.io/v1alpha1
kindApp
Applied bysng apply (manifest) or sng app create, patch, scale, restart
Related commandssng app list · describe · logs · delete, sng template gen App

What it is for​

An App describes a set of named services, like a docker-compose.yml: an image, ports, variables, a volume, a dependency between services. The platform turns it into containers running on your tenant, a web service exposed over HTTPS at https://<service>-<app>.<tenant>.dev.stackngo.app/, one-off tasks (job), or scheduled tasks (cronjob). The network is closed by default, containers run without privileges, and the state of each service can be read in status.services.

Minimal example​

web.yml
apiVersion: deploy.stackngo.io/v1alpha1
kind: App
metadata:
name: web
spec:
services:
web:
image: nginxinc/nginx-unprivileged:1.27
ports: ["8080"]
ingress: { port: 8080 }
sng apply web.yml
sng app describe web # https://web-web.<tenant>.dev.stackngo.app/

Equivalent without a manifest: sng app create web --image nginxinc/nginx-unprivileged:1.27 --port 8080 --ingress.

Complete annotated example​

shop.yml
apiVersion: deploy.stackngo.io/v1alpha1
kind: App
metadata:
name: shop # DNS label, at most 24 characters
spec:
imagePullSecrets: [gitlab-registry] # docker-registry Secrets used by all services;
# not needed for the platform registry (see below)
services:
api:
image: registry.gitlab.com/stackngo/shop-api:1.4.2
ports: ["8080"] # "8080" or "8080:80" (Service port:container port), /udp suffix allowed
env:
LOG_LEVEL: info # string
WORKERS: 4 # number: converted to "4"
DB_PASSWORD: { secret: db, key: password } # key of a Secret, never the plaintext value
envFrom: [shop-api] # every key of the shop-api Secret becomes a variable
dependsOn: [migrate, db] # waits for migrate to finish and db to be ready
healthcheck: { http: /healthz, interval: 15s } # readiness + liveness probe
pullPolicy: always # always, missing, or never
replicas: 2
strategy: { type: rolling, maxSurge: 1, maxUnavailable: 0 } # zero-downtime update
size: xs # 500m CPU / 1Gi reserved, limits at twice that
ingress: { port: 8080, path: / } # https://api-shop.<tenant>.dev.stackngo.app/
autoscale: { max: 6, cpu: 70 } # from 2 (replicas) to 6 instances, 70% CPU target

migrate:
kind: job # runs once, rerun whenever its spec changes
image: registry.gitlab.com/stackngo/shop-api:1.4.2
command: ["shop", "migrate"] # replaces the image's CMD
envFrom: [shop-api]
env:
DB_PASSWORD: { secret: db, key: password }
dependsOn: [db]

cleanup:
kind: cronjob
schedule: "0 3 * * *" # required for a cronjob, forbidden otherwise
image: registry.gitlab.com/stackngo/shop-api:1.4.2
command: ["shop", "gc"]
envFrom: [shop-api]

db:
image: postgres:17
imagePullSecrets: [] # this service uses no registry Secret
user: "999" # numeric non-root uid (runAsUser, runAsGroup, fsGroup)
ports: ["5432"] # reachable by the App's other services: db:5432
env:
PGDATA: /var/lib/postgresql/data/pgdata
POSTGRES_PASSWORD: { secret: db, key: password }
healthcheck: { cmd: ["pg_isready", "-U", "postgres"] }
volumes:
- { path: /var/lib/postgresql/data, size: 10Gi } # persistent volume; forces 1 replica

Fields​

Cross-field validation rules​

The schema rejects a manifest that breaks one of these rules, as early as sng apply:

  • The App name and each service name are DNS labels of at most 24 characters (^[a-z](./[-a-z0-9]{0,22}[a-z0-9])?$). An App has 1 to 50 services.
  • schedule is required for kind: cronjob and forbidden for the other kinds.
  • ingress, strategy, replicas, autoscale, allowFrom, and restartedAt apply only to kind: deployment (the default kind).
  • A service with volumes runs as a single instance: replicas ≤ 1 and no autoscale.
  • A service with volumes that declares a strategy must choose type: recreate.
  • autoscale.max must be greater than or equal to replicas (1 if absent).
  • healthcheck contains exactly one of http, tcp, or cmd.
  • strategy.maxSurge and strategy.maxUnavailable apply only to type: rolling.
  • user is a numeric "uid" or "uid:gid", with a non-zero uid (root is rejected).
  • egress accepts only the value internet; size accepts only xxxs … xxxl.

Other rules are checked after the manifest is saved. On a violation, the App goes to Ready=False with the reason Invalid:

  • environment variable names of the form ^[A-Za-z_][A-Za-z0-9_.-]*$, values of type string, number, boolean, or {secret, key} (null is rejected);
  • ports only on a deployment, without duplicates, of the form "8080", "8080:80", or "53/udp" (no host address);
  • ingress requires a port: non-empty ports, and ingress.port present in ports;
  • healthcheck.http requires ports or healthcheck.port;
  • allowFrom requires at least one declared port;
  • strategy: rolling with maxSurge: 0 and maxUnavailable: 0 is rejected;
  • dependsOn targets neither the service itself, nor an unknown service, nor a cronjob (it has no "finished" state), and does not form a cycle.

The API also rejects, as early as sng apply: an ingress without ports, and an ingress.domain that is not of the form <nom>.<tenant>.dev.stackngo.app (see Exposure and domains).

App​

App déploie un ensemble de services décrits façon docker compose.

ChampDescriptionDéfautValidation
apiVersion stringdeploy.stackngo.io/v1alpha1
kind stringApp
metadata ObjectMetaSeul metadata.name est à renseigner (nom de la ressource dans votre tenant).
spec AppSpec
status AppStatus

AppSpec​

AppSpec décrit votre application façon docker compose : un ensemble de services nommés.

ChampDescriptionDéfautValidation
imagePullSecrets string arrayImagePullSecrets : noms des secrets d'accès à un registre d'images privé, utilisés par défaut par tous les services.MaxItems: 10
Optional: {}
services object (keys:string, values:Service)Services de l'application, indexés par nom (nom DNS court, ex. api, db).MaxProperties: 50
MinProperties: 1

AppStatus​

AppStatus : état observé de l'application.

ChampDescriptionDéfautValidation
observedGeneration integerDernière version du manifeste prise en compte par la plateforme. Quand elle égale metadata.generation, le statut reflète votre dernier sng apply.Optional: {}
conditions Condition arrayConditions de l'application (Ready, Progressing…), chacune avec son status, sa reason et un message lisible.Optional: {}
services object (keys:string, values:ServiceStatus)État de chaque service de l'application, indexé par son nom.Optional: {}
summary stringSummary : services prêts / total, ex. 3/4.Optional: {}

Service​

Service est un conteneur de l'application : service permanent (deployment), tâche ponctuelle (job) ou tâche planifiée (cronjob).

ChampDescriptionDéfautValidation
kind stringKind : deployment (défaut), job ou cronjob.deploymentEnum: [deployment job cronjob]
Optional: {}
image stringImage du conteneur.MaxLength: 512
MinLength: 1
command string arrayCommand remplace le CMD de l'image (comme command en compose).MaxItems: 64
Optional: {}
entrypoint string arrayEntrypoint remplace l'ENTRYPOINT de l'image.MaxItems: 64
Optional: {}
user stringUser : "uid" ou "uid:gid" numériques sous lesquels tourne le conteneur ; le groupe est aussi
propriétaire des fichiers des volumes. Root (0) est refusé.
Pattern: ^[1-9][0-9]\{0,9\}(:[0-9]\{1,10\})?$
Optional: {}
imagePullSecrets stringImagePullSecrets remplace spec.imagePullSecrets pour ce service ([] = aucun).MaxItems: 10
Optional: {}
pullPolicy stringPullPolicy : always, missing ou never (valeurs compose).Enum: [always missing never]
Optional: {}
env object (keys:string, values:EnvValue)Env : NOM: valeur, ou NOM: {secret: <nom du secret>, key: <clé>}.MaxProperties: 100
Schemaless: {}
Type: object
Optional: {}
envFrom string arrayEnvFrom : secrets de votre tenant dont toutes les clés deviennent des variables d'environnement.MaxItems: 10
Optional: {}
ports IntOrString arrayPorts : "8080" ou "8080:80" (port exposé:port du conteneur), suffixe /udp possible.MaxItems: 20
Optional: {}
volumes Volume arrayVolumes persistants montés dans le conteneur.MaxItems: 10
Optional: {}
dependsOn string arrayDependsOn : services de l'application qui doivent être prêts (deployment) ou terminés (job) avant celui-ci.MaxItems: 20
Optional: {}
init string arrayInit : commande lancée avant chaque démarrage d'un exemplaire du service (même image, même environnement).MaxItems: 64
Optional: {}
healthcheck HealthcheckHealthcheck : test de santé. Un exemplaire en échec ne reçoit plus de trafic ; après retries
échecs consécutifs, son conteneur est redémarré.
Optional: {}
replicas integerReplicas : nombre d'exemplaires du service (deployment uniquement), défaut 1.Maximum: 100
Minimum: 0
Optional: {}
strategy StrategyStrategy de mise à jour (deployment uniquement).Optional: {}
size stringSize : taille du conteneur (réservations ; limites = 2 × réservations). Défaut xxs.Enum: [xxxs xxs xs s m l xl xxl xxxl]
Optional: {}
schedule stringSchedule cron (kind: cronjob uniquement).MaxLength: 128
MinLength: 1
Optional: {}
ingress IngressIngress : exposition publique en HTTP(S) (deployment uniquement).Optional: {}
autoscale AutoscaleAutoscale : ajuste le nombre d'exemplaires selon l'utilisation CPU, entre replicas et autoscale.max.Optional: {}
egress string arrayEgress : sorties ouvertes en plus des services de votre tenant et du DNS. "internet" ouvre
Internet, jamais les réseaux internes de la plateforme.
MaxItems: 1
items:Enum: [internet]
Optional: {}
allowFrom string arrayAllowFrom : Apps du même tenant autorisées à joindre les ports de ce service.MaxItems: 10
items:Pattern: ^[a-z]([-a-z0-9]\{0,22\}[a-z0-9])?$
Optional: {}
restartedAt stringRestartedAt : changer cette valeur redémarre tous les exemplaires du service (sng app restart).MaxLength: 64
Optional: {}

ServiceStatus​

ServiceStatus : état observé d'un service.

ChampDescriptionDéfautValidation
kind stringType du service (service permanent, tâche ponctuelle ou tâche planifiée).
ready booleanReady : deployment disponible et à jour, job terminé, cronjob planifié.
phase stringPhase : Ready, Progressing, Waiting, Failed ou Scheduled.
reason stringRaison courte de l'état courant, utile quand le service n'est pas prêt.Optional: {}
message stringMessage détaillé associé à la raison.Optional: {}
url stringAdresse publique du service lorsqu'il est exposé.Optional: {}

EnvValue​

EnvValue est la valeur d'une variable d'environnement : une chaîne (les nombres et booléens compose sont acceptés et convertis), ou une référence {secret, key}.

ChampDescriptionDéfautValidation
invalid stringRéservé à la plateforme. Ne pas renseigner : une valeur de variable d'environnement s'écrit en texte ou avec secretKeyRef.
raw integer arrayRéservé à la plateforme. Ne pas renseigner.

Volume​

Volume persistant, monté par un seul exemplaire à la fois.

ChampDescriptionDéfautValidation
path stringPath de montage dans le conteneur.MaxLength: 256
Pattern: ^/
size QuantitySize du volume, ex. 10Gi.

Healthcheck​

Healthcheck : exactement un de http, tcp ou cmd.

ChampDescriptionDéfautValidation
http stringHTTP : chemin testé en GET, ex. /healthz.MaxLength: 256
Pattern: ^/
Optional: {}
port integerPort du test http (défaut : premier port du service).Maximum: 65535
Minimum: 1
Optional: {}
tcp integerTCP : port testé en connexion TCP.Maximum: 65535
Minimum: 1
Optional: {}
cmd string arrayCmd : commande exécutée dans le conteneur, code 0 = sain.MaxItems: 32
Optional: {}
interval DurationInterval entre deux tests (défaut 10s).Optional: {}
timeout DurationTimeout d'un test (défaut 5s).Optional: {}
retries integerRetries : échecs consécutifs avant de déclarer le conteneur malade (défaut 3).Maximum: 30
Minimum: 1
Optional: {}
startPeriod DurationStartPeriod : délai de démarrage pendant lequel un échec du test ne redémarre pas le conteneur.Optional: {}

Strategy​

Strategy de mise à jour d'un deployment.

ChampDescriptionDéfautValidation
type stringType : rolling ou recreate.Enum: [rolling recreate]
maxSurge IntOrStringMaxSurge : exemplaires en plus pendant la mise à jour (nombre ou %), défaut 1.Optional: {}
maxUnavailable IntOrStringMaxUnavailable : exemplaires indisponibles tolérés (nombre ou %), défaut 0.Optional: {}

Ingress​

Ingress public du service.

ChampDescriptionDéfautValidation
domain stringDomain : défaut <service>-<app>.<tenant>.<domaine de base>.MaxLength: 253
Pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)+$
Optional: {}
port integerPort du service ciblé (défaut : premier port).Maximum: 65535
Minimum: 1
Optional: {}
path stringPath préfixe, défaut /.MaxLength: 256
Pattern: ^/
Optional: {}

Autoscale​

Autoscale : ajustement automatique du nombre d'exemplaires selon l'utilisation CPU.

ChampDescriptionDéfautValidation
max integerMax : nombre maximal d'exemplaires.Maximum: 100
Minimum: 1
cpu integerCPU : utilisation cible en % des réservations (défaut 80).Maximum: 100
Minimum: 1
Optional: {}

Images from the platform registry. An image <cluster>.registry.dev.stackngo.fr/<tenant>/… (the host of the App's cluster, for example ovh-gra-0.registry.dev.stackngo.fr) is pulled without declaring anything, with the tenant's read access (default-pull robot), as long as neither the service nor the App gives imagePullSecrets. As soon as a list is given (even empty, [], at the service level), it replaces this access: to mix an image from an external registry and an image from the platform, give the external registry's imagePullSecrets to the service concerned rather than to the App. An image from another cluster's registry is rejected (image … is hosted on the registry of cluster …). See Push and pull images.

Status and conditions​

status.summary gives the number of ready services out of the total (3/4). status.services.<nom> details each service: kind, ready, phase, reason, message, and url (exposed service).

Service phaseMeaning
Readydeployment available and up to date (and, if exposed, HTTPS certificate issued), or job finished
Progressingdeployment in progress, HTTPS certificate being issued, job running
Waitingblocked by a dependency, a missing registry Secret or registry access not ready yet; nothing is applied
Failedrejection (domain, conflict, domain name already taken, quota) or failure (job, deployment that does not complete)
Scheduledcronjob scheduled (counts as ready)
ConditionTrue whenFalse when
Readyall services are Ready or Scheduledat least one service is not; reason and message reflect the service in the most trouble (failure, then waiting, then deployment)
Progressingat least one service is Progressing or Waiting (reason RollingOut)nothing is in progress (Reconciled)
Degradedthe App is invalid or at least one service is Failedno failure (Reconciled)
reasonWhereExplanationAction
ReconciledApp, serviceEverything is applied and ready—
InvalidApp, serviceA rule checked after saving was broken (see above); the message names the fieldFix the manifest, sng apply
DomainNotAllowedserviceingress.domain is outside your zone, or is a reserved name (status.<zone>)Choose <nom>.<tenant>.dev.stackngo.app or remove domain
HostConflictserviceThe hostname is already served elsewhere (another tenant, or another App of the tenant on the same path)Change the service name, the App name, or the path
NameConflictserviceAn item with the same name already exists in your tenant and does not belong to this AppRename the service or the App
DNSConflictservice (Failed)The domain name is already published in DNS by someone else (a DNSRecord, for example): Le nom de domaine … est déjà utilisé : l'App n'est pas joignable à cette adresse tant qu'il n'est pas libéré. The services that depend on it start anywayFree the name (delete the record) or change ingress.domain; the service recovers on its own
CertificatePendingservice (Progressing)The service responds, but its HTTPS certificate is not issued yet: Le certificat HTTPS de l'App est en cours d'émission. The services that depend on it start without waiting for itWait, usually a few minutes; for a brought domain, check that the zone is Active
ImagePullSecretNotFoundservice (Waiting)A Secret in imagePullSecrets does not exist; the service is not created, rechecked every 30 sCreate the Secret or fix the name
RegistryAccessPendingservice (Waiting)The image comes from the platform registry and the tenant's read access is not ready yet (tenant just created): l'accès au registre d'images est en cours de préparation, nouvelle tentative automatiqueWait: the service starts on its own
SecretNotFoundservice (Progressing)A Secret in env / envFrom does not exist; the containers do not start while it is missingCreate the Secret (Injection into an App)
WaitingForDependencyservice (Waiting)A service in dependsOn is not ready or finished yetWait, or look at the service depended on
DependencyFailedservice (Waiting)A service in dependsOn has failedFix the service depended on
QuotaExceededservice (Failed or Progressing)The requested size exceeds your plan's quotaReduce size or replicas, or change plan
ApplyFailedserviceThe platform could not apply the service; the message gives the errorReread the message; contact support if it persists
RollingOutservice (Progressing)Update in progress: n/m à jour, k disponiblesWait; sng app logs if it takes too long
RolloutFailedservice (Failed)The deployment did not complete in time (containers that do not start)sng app logs, check the image, user, healthcheck
JobRunningservice (Progressing)The job is runningWait
JobFailedservice (Failed)The job failed after 3 attemptssng app logs --service <job>, fix, sng apply

What happens when you delete​

A service removed from the manifest stops and can no longer be reached; its HTTPS address stops responding. Its volumes are kept with their data.

A deleted App (sng app delete <app> or sng kind delete App <app>): all its services stop, its addresses stop responding, its jobs and cronjobs no longer run. The volumes are kept with their data; no deletionPolicy field lets you delete them automatically in this version. To have them deleted, ask support.

On update, a deployment follows its strategy (by default rolling: one extra instance, none unavailable), a job whose spec changes is rerun, and sng app restart restarts a service's instances without changing anything else. A service that is waiting (Waiting) or rejected (Failed) keeps its previous version if one existed.

Common errors​

SymptomCauseFix
sng apply rejects: schedule est requis pour kind: cronjob et interdit sinonschedule on a service that is not a cronjob, or a cronjob without scheduleAdd kind: cronjob or remove schedule
sng apply rejects: un service avec volume tourne en un seul exemplairereplicas: 2 or autoscale on a service with volumesRemove replicas/autoscale, or move the state out of the service (managed database, object storage)
sng apply rejects: champs inconnus : spec.services.web.privilegedField outside the schema (often an untranslated compose key)Remove the field; see From docker compose
sng apply rejects: ingress.domain "…" : seul <nom>.<tenant>.dev.stackngo.app est autoriséDomain outside your zoneUse a name under your zone, or keep the default domain
Service Progressing, RollingOut that never finishes, then RolloutFailedThe image starts as root (rejected), or the healthcheck probe failsNon-root image or user: "<uid>"; check the probe's path and port; sng app logs
Service Waiting, ImagePullSecretNotFoundThe registry Secret does not exist under that nameCreate the docker-registry Secret, or remove imagePullSecrets (public image, or image from the platform registry)
Service Waiting, RegistryAccessPendingImage from the platform registry, tenant access not ready yetWait; report it if it lasts more than a few minutes
Service Progressing, CertificatePendingHTTPS certificate being issuedWait; if it lasts more than an hour, contact support
Service Failed, DNSConflict: Le nom de domaine … est déjà utiliséA DNS record already publishes this nameDelete the record, or choose another domain
Service Progressing, SecretNotFoundA Secret in env/envFrom is missingCreate it; the containers start as soon as it exists
Service Failed, QuotaExceededsize × replicas exceeds the plan's quota (limits count 2 × the reservations)Reduce the size, or change plan
Service Failed, HostConflict: le host … est déjà utiliséAnother tenant serves this hostnameChoose another App or service name

See also​