App
Deployed and supported. The fields and commands described here are stable within version v1alpha1.
apiVersion | deploy.stackngo.io/v1alpha1 |
kind | App |
| Applied by | sng apply (manifest) or sng app create, patch, scale, restart |
| Related commands | sng 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
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
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. scheduleis required forkind: cronjoband forbidden for the other kinds.ingress,strategy,replicas,autoscale,allowFrom, andrestartedAtapply only tokind: deployment(the default kind).- A service with
volumesruns as a single instance:replicas≤ 1 and noautoscale. - A service with
volumesthat declares astrategymust choosetype: recreate. autoscale.maxmust be greater than or equal toreplicas(1 if absent).healthcheckcontains exactly one ofhttp,tcp, orcmd.strategy.maxSurgeandstrategy.maxUnavailableapply only totype: rolling.useris a numeric"uid"or"uid:gid", with a non-zero uid (root is rejected).egressaccepts only the valueinternet;sizeaccepts onlyxxxs…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}(nullis rejected); portsonly on adeployment, without duplicates, of the form"8080","8080:80", or"53/udp"(no host address);ingressrequires a port: non-emptyports, andingress.portpresent inports;healthcheck.httprequiresportsorhealthcheck.port;allowFromrequires at least one declared port;strategy: rollingwithmaxSurge: 0andmaxUnavailable: 0is rejected;dependsOntargets neither the service itself, nor an unknown service, nor acronjob(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.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
apiVersion string | deploy.stackngo.io/v1alpha1 | ||
kind string | App | ||
metadata ObjectMeta | Seul 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.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
imagePullSecrets string array | ImagePullSecrets : 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.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
observedGeneration integer | Derniè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 array | Conditions 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 string | Summary : 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).
| Champ | Description | Défaut | Validation |
|---|---|---|---|
kind string | Kind : deployment (défaut), job ou cronjob. | deployment | Enum: [deployment job cronjob] Optional: {} |
image string | Image du conteneur. | MaxLength: 512 MinLength: 1 | |
command string array | Command remplace le CMD de l'image (comme command en compose). | MaxItems: 64 Optional: {} | |
entrypoint string array | Entrypoint remplace l'ENTRYPOINT de l'image. | MaxItems: 64 Optional: {} | |
user string | User : "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 string | ImagePullSecrets remplace spec.imagePullSecrets pour ce service ([] = aucun). | MaxItems: 10 Optional: {} | |
pullPolicy string | PullPolicy : 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 array | EnvFrom : secrets de votre tenant dont toutes les clés deviennent des variables d'environnement. | MaxItems: 10 Optional: {} | |
ports IntOrString array | Ports : "8080" ou "8080:80" (port exposé:port du conteneur), suffixe /udp possible. | MaxItems: 20 Optional: {} | |
volumes Volume array | Volumes persistants montés dans le conteneur. | MaxItems: 10 Optional: {} | |
dependsOn string array | DependsOn : services de l'application qui doivent être prêts (deployment) ou terminés (job) avant celui-ci. | MaxItems: 20 Optional: {} | |
init string array | Init : commande lancée avant chaque démarrage d'un exemplaire du service (même image, même environnement). | MaxItems: 64 Optional: {} | |
healthcheck Healthcheck | Healthcheck : 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 integer | Replicas : nombre d'exemplaires du service (deployment uniquement), défaut 1. | Maximum: 100 Minimum: 0 Optional: {} | |
strategy Strategy | Strategy de mise à jour (deployment uniquement). | Optional: {} | |
size string | Size : taille du conteneur (réservations ; limites = 2 × réservations). Défaut xxs. | Enum: [xxxs xxs xs s m l xl xxl xxxl] Optional: {} | |
schedule string | Schedule cron (kind: cronjob uniquement). | MaxLength: 128 MinLength: 1 Optional: {} | |
ingress Ingress | Ingress : exposition publique en HTTP(S) (deployment uniquement). | Optional: {} | |
autoscale Autoscale | Autoscale : ajuste le nombre d'exemplaires selon l'utilisation CPU, entre replicas et autoscale.max. | Optional: {} | |
egress string array | Egress : 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 array | AllowFrom : 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 string | RestartedAt : changer cette valeur redémarre tous les exemplaires du service (sng app restart). | MaxLength: 64 Optional: {} |
ServiceStatus
ServiceStatus : état observé d'un service.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
kind string | Type du service (service permanent, tâche ponctuelle ou tâche planifiée). | ||
ready boolean | Ready : deployment disponible et à jour, job terminé, cronjob planifié. | ||
phase string | Phase : Ready, Progressing, Waiting, Failed ou Scheduled. | ||
reason string | Raison courte de l'état courant, utile quand le service n'est pas prêt. | Optional: {} | |
message string | Message détaillé associé à la raison. | Optional: {} | |
url string | Adresse 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}.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
invalid string | Réservé à la plateforme. Ne pas renseigner : une valeur de variable d'environnement s'écrit en texte ou avec secretKeyRef. | ||
raw integer array | Réservé à la plateforme. Ne pas renseigner. |
Volume
Volume persistant, monté par un seul exemplaire à la fois.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
path string | Path de montage dans le conteneur. | MaxLength: 256 Pattern: ^/ | |
size Quantity | Size du volume, ex. 10Gi. |
Healthcheck
Healthcheck : exactement un de http, tcp ou cmd.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
http string | HTTP : chemin testé en GET, ex. /healthz. | MaxLength: 256 Pattern: ^/ Optional: {} | |
port integer | Port du test http (défaut : premier port du service). | Maximum: 65535 Minimum: 1 Optional: {} | |
tcp integer | TCP : port testé en connexion TCP. | Maximum: 65535 Minimum: 1 Optional: {} | |
cmd string array | Cmd : commande exécutée dans le conteneur, code 0 = sain. | MaxItems: 32 Optional: {} | |
interval Duration | Interval entre deux tests (défaut 10s). | Optional: {} | |
timeout Duration | Timeout d'un test (défaut 5s). | Optional: {} | |
retries integer | Retries : échecs consécutifs avant de déclarer le conteneur malade (défaut 3). | Maximum: 30 Minimum: 1 Optional: {} | |
startPeriod Duration | StartPeriod : 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.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
type string | Type : rolling ou recreate. | Enum: [rolling recreate] | |
maxSurge IntOrString | MaxSurge : exemplaires en plus pendant la mise à jour (nombre ou %), défaut 1. | Optional: {} | |
maxUnavailable IntOrString | MaxUnavailable : exemplaires indisponibles tolérés (nombre ou %), défaut 0. | Optional: {} |
Ingress
Ingress public du service.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
domain string | Domain : 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 integer | Port du service ciblé (défaut : premier port). | Maximum: 65535 Minimum: 1 Optional: {} | |
path string | Path préfixe, défaut /. | MaxLength: 256 Pattern: ^/ Optional: {} |
Autoscale
Autoscale : ajustement automatique du nombre d'exemplaires selon l'utilisation CPU.
| Champ | Description | Défaut | Validation |
|---|---|---|---|
max integer | Max : nombre maximal d'exemplaires. | Maximum: 100 Minimum: 1 | |
cpu integer | CPU : 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 phase | Meaning |
|---|---|
Ready | deployment available and up to date (and, if exposed, HTTPS certificate issued), or job finished |
Progressing | deployment in progress, HTTPS certificate being issued, job running |
Waiting | blocked by a dependency, a missing registry Secret or registry access not ready yet; nothing is applied |
Failed | rejection (domain, conflict, domain name already taken, quota) or failure (job, deployment that does not complete) |
Scheduled | cronjob scheduled (counts as ready) |
| Condition | True when | False when |
|---|---|---|
Ready | all services are Ready or Scheduled | at least one service is not; reason and message reflect the service in the most trouble (failure, then waiting, then deployment) |
Progressing | at least one service is Progressing or Waiting (reason RollingOut) | nothing is in progress (Reconciled) |
Degraded | the App is invalid or at least one service is Failed | no failure (Reconciled) |
reason | Where | Explanation | Action |
|---|---|---|---|
Reconciled | App, service | Everything is applied and ready | — |
Invalid | App, service | A rule checked after saving was broken (see above); the message names the field | Fix the manifest, sng apply |
DomainNotAllowed | service | ingress.domain is outside your zone, or is a reserved name (status.<zone>) | Choose <nom>.<tenant>.dev.stackngo.app or remove domain |
HostConflict | service | The 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 |
NameConflict | service | An item with the same name already exists in your tenant and does not belong to this App | Rename the service or the App |
DNSConflict | service (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 anyway | Free the name (delete the record) or change ingress.domain; the service recovers on its own |
CertificatePending | service (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 it | Wait, usually a few minutes; for a brought domain, check that the zone is Active |
ImagePullSecretNotFound | service (Waiting) | A Secret in imagePullSecrets does not exist; the service is not created, rechecked every 30 s | Create the Secret or fix the name |
RegistryAccessPending | service (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 automatique | Wait: the service starts on its own |
SecretNotFound | service (Progressing) | A Secret in env / envFrom does not exist; the containers do not start while it is missing | Create the Secret (Injection into an App) |
WaitingForDependency | service (Waiting) | A service in dependsOn is not ready or finished yet | Wait, or look at the service depended on |
DependencyFailed | service (Waiting) | A service in dependsOn has failed | Fix the service depended on |
QuotaExceeded | service (Failed or Progressing) | The requested size exceeds your plan's quota | Reduce size or replicas, or change plan |
ApplyFailed | service | The platform could not apply the service; the message gives the error | Reread the message; contact support if it persists |
RollingOut | service (Progressing) | Update in progress: n/m à jour, k disponibles | Wait; sng app logs if it takes too long |
RolloutFailed | service (Failed) | The deployment did not complete in time (containers that do not start) | sng app logs, check the image, user, healthcheck |
JobRunning | service (Progressing) | The job is running | Wait |
JobFailed | service (Failed) | The job failed after 3 attempts | sng 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
| Symptom | Cause | Fix |
|---|---|---|
sng apply rejects: schedule est requis pour kind: cronjob et interdit sinon | schedule on a service that is not a cronjob, or a cronjob without schedule | Add kind: cronjob or remove schedule |
sng apply rejects: un service avec volume tourne en un seul exemplaire | replicas: 2 or autoscale on a service with volumes | Remove replicas/autoscale, or move the state out of the service (managed database, object storage) |
sng apply rejects: champs inconnus : spec.services.web.privileged | Field 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 zone | Use a name under your zone, or keep the default domain |
Service Progressing, RollingOut that never finishes, then RolloutFailed | The image starts as root (rejected), or the healthcheck probe fails | Non-root image or user: "<uid>"; check the probe's path and port; sng app logs |
Service Waiting, ImagePullSecretNotFound | The registry Secret does not exist under that name | Create the docker-registry Secret, or remove imagePullSecrets (public image, or image from the platform registry) |
Service Waiting, RegistryAccessPending | Image from the platform registry, tenant access not ready yet | Wait; report it if it lasts more than a few minutes |
Service Progressing, CertificatePending | HTTPS certificate being issued | Wait; 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 name | Delete the record, or choose another domain |
Service Progressing, SecretNotFound | A Secret in env/envFrom is missing | Create it; the containers start as soon as it exists |
Service Failed, QuotaExceeded | size × 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 hostname | Choose another App or service name |
See also
- Guides: From docker compose, Services, jobs, and cronjobs, Variables and secrets, Storage, Exposure and domains, Healthchecks and scaling, Sizes and resources, Network, Operations.
- CLI:
sng app, global flags and desired-state commands. - Other kinds:
SSMSecret(injected secrets),DNSZone(domains you bring, Beta).