Depuis docker compose
Fonctionnalité déployée et prise en charge. Les champs et commandes décrits ici sont stables au sein de la version v1alpha1.
La conversion automatique d'un fichier compose n'est pas encore proposée en ligne de commande : ce guide donne la correspondance à appliquer à la main. Elle reprend exactement les règles du convertisseur de la plateforme.
Un manifeste App se lit comme un docker-compose.yml : des services nommés, une image, des
ports, des variables, des volumes, des dépendances. Ce guide vous donne la correspondance
champ par champ pour passer de l'un à l'autre, et liste ce qui ne se traduit pas.
Prérequis
- Une session ouverte et un tenant choisi :
sng sso login,sng tenant use. - Vos images construites et poussées dans un registre joignable depuis la plateforme
(
build:n'est pas traduit). - Pour une image privée : un Secret docker-registry sur votre tenant (Variables et secrets).
Étapes
1. Partir du gabarit
sng template gen App shop
créé /home/vous/shop.yml (kind=App)
Le fichier contient un service web commenté ; remplacez-le par vos services.
2. Traduire service par service
Prenons ce docker-compose.yml :
name: shop
services:
api:
image: registry.gitlab.com/stackngo/shop-api:1.4.2
command: shop serve --port 8080
ports: ["8080"]
environment:
LOG_LEVEL: info
WORKERS: 4
env_file: [.env]
depends_on:
migrate: { condition: service_completed_successfully }
db: { condition: service_healthy }
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/healthz"]
interval: 15s
retries: 5
deploy:
replicas: 2
resources:
reservations: { cpus: "0.25", memory: 256M }
update_config: { order: start-first }
migrate:
image: registry.gitlab.com/stackngo/shop-api:1.4.2
command: ["shop", "migrate"]
depends_on: [db]
db:
image: postgres:17
user: "999"
expose: ["5432"]
environment:
POSTGRES_PASSWORD: changeme
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
volumes:
pgdata:
Sa traduction :
apiVersion: deploy.stackngo.io/v1alpha1
kind: App
metadata:
name: shop # name du projet compose
spec:
imagePullSecrets: [gitlab-registry] # registre privé : Secret à créer sur le tenant
services:
api:
image: registry.gitlab.com/stackngo/shop-api:1.4.2
command: ["shop", "serve", "--port", "8080"]
ports: ["8080"]
env:
LOG_LEVEL: info
WORKERS: 4
envFrom: [api-env] # env_file → Secret api-env, à créer
dependsOn: [db, migrate] # migrate attendu « terminé » → migrate devient un job
healthcheck:
cmd: ["wget", "-qO-", "http://127.0.0.1:8080/healthz"]
interval: 15s
retries: 5
replicas: 2
size: xs # 0.25 CPU / 256M → plus petite taille qui couvre
strategy: { type: rolling, maxSurge: 1, maxUnavailable: 0 } # order: start-first
ingress: { port: 8080 } # compose n'a pas d'équivalent : à ajouter
migrate:
kind: job # attendu en service_completed_successfully
image: registry.gitlab.com/stackngo/shop-api:1.4.2
command: ["shop", "migrate"]
dependsOn: [db]
db:
image: postgres:17
user: "999"
ports: ["5432"] # expose → ports (joignable en db:5432 par les autres services)
env:
POSTGRES_PASSWORD: { secret: db, key: password } # jamais la valeur en clair
healthcheck: { cmd: ["sh", "-c", "pg_isready -U postgres"] }
volumes:
- { path: /var/lib/postgresql/data, size: 10Gi } # volume nommé → volume persistant, taille à choisir
3. Créer les Secrets attendus
Le manifeste référence gitlab-registry, api-env et db. Créez-les avant ou juste après
l'apply : voir Injection dans une App. Tant qu'un
Secret d'environnement manque, le service reste en Progressing avec la raison SecretNotFound ;
un Secret de registre manquant bloque le service en Waiting (ImagePullSecretNotFound).
4. Appliquer et suivre
sng apply shop.yml
sng app describe shop
App : shop
Statut : en cours
Message : api : attend : migrate
SERVICE KIND IMAGE PHASE URL DETAIL
api deployment registry.gitlab.com/stackngo/shop-api:1.4.2 Waiting - WaitingForDependency attend : migrate
db deployment postgres:17 Ready - Reconciled 1/1 prêts
migrate job registry.gitlab.com/stackngo/shop-api:1.4.2 Progressing - JobRunning job en cours
Vérification
sng app describe shop affiche Statut : prête et, pour api, l'URL
https://api-shop.<tenant>.dev.stackngo.app/. curl -I sur cette URL répond HTTP/2 200.
Correspondance champ par champ
| compose | App | Remarque |
|---|---|---|
name (projet) | metadata.name | minuscules, _ et . deviennent -, 24 caractères au plus |
| nom de service | clé de spec.services | même normalisation ; cache_store → cache-store |
image | image | requis |
build | — | ignoré : construisez et poussez l'image, puis renseignez image |
command | command | forme liste ; une chaîne compose est découpée comme par le shell |
entrypoint | entrypoint | |
user: "999", "1000:1000" | user | seulement un uid numérique non root ; user: redis est ignoré |
environment: K: v | env: K: v | valeur recopiée ; ${VAR} n'est pas interpolé (remplacez la valeur) |
environment: K: (sans valeur) | — | ignoré : la valeur venait de votre machine |
env_file: [.env] | envFrom: [<service>-env] | le fichier n'est pas recopié : créez le Secret <service>-env avec son contenu |
valeur qui ressemble à un secret (PASSWORD, TOKEN, KEY…) | env: K: { secret: s, key: k } | mettez-la dans un Secret |
ports: "8080", "80:8080", "53:53/udp" | ports | publié:cible ; host_ip ignoré ; une plage (8000-8010) est refusée |
expose: ["5432"] | ports: ["5432"] | même chose : un port déclaré est joignable par les autres services de l'App |
volumes: nom:/chemin | volumes: [{ path: /chemin, size: 1Gi }] | volume persistant ; taille à ajuster (1Gi par défaut) ; impose un seul réplica |
volumes: ./fichier:/chemin (bind), tmpfs | — | ignorés : seuls les volumes nommés deviennent des volumes |
depends_on: [db] | dependsOn: [db] | la condition service_healthy est implicite (le service doit être prêt) |
depends_on: x: { condition: service_completed_successfully } | dependsOn: [x] et x devient kind: job | |
healthcheck.test: ["CMD", …] | healthcheck.cmd: […] | |
healthcheck.test: ["CMD-SHELL", "…"] | healthcheck.cmd: ["sh", "-c", "…"] | |
interval, timeout, start_period, retries | interval, timeout, startPeriod, retries | retries borné à 1–30 |
healthcheck: disable: true ou test: ["NONE"] | — | pas de sonde |
pull_policy: always / missing, if_not_present / never | pullPolicy: always / missing / never | |
deploy.replicas: n | replicas: n | 0–100 ; ramené à 1 si le service a un volume |
deploy.resources.reservations (ou limits à défaut) | size | plus petite taille dont les réservations couvrent CPU et mémoire ; au-delà de xxxl, xxxl |
deploy.update_config: order: start-first | strategy: { type: rolling, maxSurge: <parallelism ou 1>, maxUnavailable: 0 } | pas de strategy si le service a un volume (il est en recreate) |
deploy.update_config: order: stop-first (ou absent) | strategy: { type: rolling, maxSurge: 0, maxUnavailable: <parallelism ou 1> } | |
profiles | — | choisissez les services à garder |
| — | ingress | aucun équivalent compose : à ajouter sur le service web |
| — | egress: [internet] | compose laisse sortir vers Internet ; ici c'est fermé par défaut |
| — | size | défaut xxs si vous ne précisez rien |
Ce qui n'est pas supporté
Ces clés compose n'ont pas d'équivalent et sont écartées, le plus souvent parce qu'elles contredisent la sécurité par défaut (conteneur sans privilège, réseau fermé) :
privileged,cap_add,network_mode,pid,devices,tmpfs;secretsmontés en fichier et la sectionsecrets:: utilisezenv: { secret, key }ouenvFrom;configset la sectionconfigs:;build, les bind mounts, les plages de ports,host_ip;restart,container_name,networks,labels,logging,extra_hosts: non repris (le redémarrage est toujours automatique, le nom du service sert de nom DNS, le réseau est celui de l'App).
Non-root. Vos conteneurs tournent sans privilège. Une image qui démarre en root (nginx,
postgres sans user) est refusée : prenez une variante non root (nginxinc/nginx-unprivileged)
ou donnez user: "<uid>".
Réseau fermé. Aucune sortie vers Internet sans egress: [internet] sur le service concerné.
Entre services d'une même App, seuls les ports déclarés sont joignables.
Pour aller plus loin
- Un service compose nommé
webdans l'Appshopest joignable par les autres services enweb:<port>, comme en compose : c'est le nom du service qui compte, pas celui de l'App. - Les tailles et leur calcul depuis
deploy.resources: Tailles et ressources. - Les règles qui peuvent refuser le manifeste : page
Appde la référence des manifestes.