Aller au contenu

Déploiement — BConnect POS

CI/CD par GitHub Actions, images stockées dans le registry Harbor de BINN, déploiement par SSH + docker compose pull sur le VPS.

Le serveur n'héberge pas le code source : il ne fait que tirer une image déjà construite. La stack ne contient que l'application — MySQL, PostgreSQL, Redis et Traefik sont mutualisés par le projet infra du VPS et joints par réseau Docker externe.

Workflow .github/workflows/deploy.yml
Registry binn.registry.169.58.138.150.nip.io (projet Harbor binn)
Image binn.registry.169.58.138.150.nip.io/binn/bconnect-pos
Image docs binn.registry.169.58.138.150.nip.io/binn/bconnect-pos-docs
Répertoire serveur /srv/apps/BConnect-POS
Stack de prod docker-compose.prod.yml (copié sur le serveur à chaque déploiement)
Réseaux externes traefik-net, infra-data, infra-messaging, bconnect-net
URL publique https://binn.pos.169.58.138.150.nip.io
URL docs https://docs.bconnect-pos.169.58.138.150.nip.io

La stack jumelle BConnect Business suit exactement le même modèle et partage le réseau bconnect-net avec celle-ci.

1. Ce qui se passe automatiquement

Un push sur main ou develop, ou un tag v*, construit l'image applicative et l'image docs, puis les pousse dans Harbor avec ces tags :

  • sha-<7 premiers caractères du commit> — le seul tag réellement immuable, c'est celui que le déploiement utilise ;
  • le nom de la branche (main, develop) ;
  • latest, uniquement sur la branche par défaut ;
  • le tag git, s'il s'agit d'un tag.

Le déploiement s'enchaîne automatiquement sur un push vers develop. Depuis toute autre ref, il se lance à la main.

2. Déployer en production

Onglet Actions → workflow Build & DeployRun workflow.

  • Use workflow from : la branche ou le tag à déployer.
  • image_tag : laisser vide. L'image est reconstruite depuis la ref choisie puis déployée. Renseigner un tag (ex. sha-a1b2c3d) uniquement pour redéployer une image déjà publiée — typiquement un retour arrière : le build est alors sauté.

Le job de déploiement est rattaché à l'environnement GitHub production. Pour exiger une approbation humaine avant qu'il ne s'exécute : Settings → Environments → production → Required reviewers.

3. Configuration GitHub (à faire une fois)

Settings → Secrets and variables → Actions

Secrets

Nom Valeur
REGISTRY_USERNAME compte Harbor (admin, ou de préférence un compte robot)
REGISTRY_PASSWORD son mot de passe / jeton
DEPLOY_HOST 169.58.138.150
DEPLOY_USER abdoulaye
DEPLOY_SSH_KEY clé privée SSH complète, sans passphrase (-----BEGIN … KEY----- inclus)
DEPLOY_PATH /srv/apps/BConnect-POS

Préférez un compte robot Harbor limité au projet binn plutôt que le compte admin : sa compromission ne donne alors accès qu'au push/pull d'images.

Variables (facultatif)

Nom Défaut Rôle
BCONNECT_DOCKER_NETWORK bconnect-net réseau Docker interne partagé avec la stack Business

Cette application est rendue côté serveur : aucune valeur destinée au navigateur n'est figée au build, tout vient du .env du serveur. Si une variable devait un jour l'être, elle passerait par une variable GitHub et un build-arg, jamais par un secret.

Environnement

Settings → Environments → New environment → production, puis y ajouter les required reviewers si vous voulez une validation manuelle.

4. Préparation du serveur (à faire une fois)

Les points a, c, d, f et le contrôle des .env sont automatisés par scripts/prepare-server.sh, à lancer sur le serveur en tant qu'utilisateur de déploiement. Il est idempotent : le relancer ne fait que revérifier. Le point e (bases MySQL) a son propre script, scripts/prepare-databases.sh. Les détails ci-dessous restent la référence, et la marche à suivre si vous préférez tout faire à la main.

bash scripts/prepare-server.sh

a. Docker et le registry

Si le certificat de Harbor n'est pas émis par une autorité reconnue — ce qui est le cas par défaut avec un nom nip.io — le docker login échoue avec une erreur x509: certificate signed by unknown authority. Déclarez le registry :

sudo tee /etc/docker/daemon.json >/dev/null <<'JSON'
{ "insecure-registries": ["binn.registry.169.58.138.150.nip.io"] }
JSON
sudo systemctl restart docker

Le workflow fait la même chose sur le runner GitHub. Le jour où Harbor a un certificat valide, ces deux configurations peuvent disparaître.

b. Clé SSH de déploiement

ssh-keygen -t ed25519 -C "github-actions-bconnect-pos" -f ~/.ssh/bconnect_pos_deploy -N ""

La partie publique va dans le ~/.ssh/authorized_keys de abdoulaye sur le serveur ; la partie privée dans le secret DEPLOY_SSH_KEY.

c. sudo sans mot de passe

Le déploiement installe le fichier compose dans un répertoire appartenant à root et pilote Docker : ces deux commandes doivent passer sans TTY ni mot de passe.

echo 'abdoulaye ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/install' \
  | sudo tee /etc/sudoers.d/bconnect-deploy
sudo chmod 440 /etc/sudoers.d/bconnect-deploy
sudo visudo -c

Le même fichier sert aux deux stacks : inutile de le créer deux fois.

d. Répertoire de déploiement

sudo install -d -o root -g root -m 755 /srv/apps/BConnect-POS

Le workflow ne le crée pas : il vérifie sa présence avec test -d et s'arrête avec un message explicite s'il manque. Un mkdir -p renverrait 0 sans rien faire et masquerait un problème de droits.

e. Base de données MySQL

À faire avant le premier déploiement. La stack ne contient pas de conteneur MySQL : la base et son utilisateur doivent exister sur l'instance mutualisée. L'image ne les crée pas — son docker/entrypoint.sh attend que la connexion réponde, puis joue doctrine:schema:update --force pour créer les tables.

CREATE DATABASE bconnect_pos CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'bconnect_pos'@'%' IDENTIFIED BY '<mot de passe>';
GRANT ALL PRIVILEGES ON bconnect_pos.* TO 'bconnect_pos'@'%';
FLUSH PRIVILEGES;

Sans cette étape, le conteneur affiche Attente de la base de donnees... puis s'arrête au bout de deux minutes en affichant l'erreur SQL réelle — c'est le symptôme à reconnaître.

f. Réseaux Docker

traefik-net, infra-data et infra-messaging sont créés et gérés par le projet infra : ne les recréez pas. infra-messaging donne accès au service SMTP mailer:1025. bconnect-net porte le trafic interne Business ↔ POS et est créé par le workflow s'il manque.

docker network ls | grep -E 'traefik-net|infra-data|infra-messaging|bconnect-net'

g. Fichier .env de production

Les secrets ne transitent jamais par GitHub : ils vivent dans un .env que vous créez à la main dans /srv/apps/BConnect-POS, à côté du docker-compose.prod.yml. Le déploiement s'arrête avec une erreur explicite si ce fichier manque.

cd /srv/apps/BConnect-POS
sudo tee .env >/dev/null <<'ENV'
# --- MySQL mutualise (reseau infra-data) ---
# DB_HOST vaut `mysql` par defaut : inutile de le redefinir tant que le service
# mutualise porte ce nom sur infra-data.
DB_HOST=mysql
DB_PORT=3306
DB_NAME=bconnect_pos
DB_USER=bconnect_pos
DB_PASSWORD=<mot de passe>
DB_SERVER_VERSION=8.0

# --- Traefik ---
POS_HOST=binn.pos.169.58.138.150.nip.io
DOCS_HOST=docs.bconnect-pos.169.58.138.150.nip.io
TRAEFIK_ENTRYPOINT=websecure
TRAEFIK_CERTRESOLVER=letsencrypt

# --- Symfony ---
APP_NAME="BConnect POS"
APP_SECRET=<openssl rand -hex 32>
CORS_ALLOW_ORIGIN=^https://binn\.(business|pos)\.169\.58\.138\.150\.nip\.io$
MAILER_DSN=smtp://mailer:1025
MAILER_FROM=noreply@binn.pro
JWT_PASSPHRASE=<openssl rand -hex 32>
LOCK_DSN=flock

# --- Liaison avec Business ---
# Appel serveur a serveur : passe par bconnect-net, sans sortir par Traefik.
BUSINESS_API_URL=http://bconnect-business
# Doit valoir exactement le SECRET_API_KEY du .env de la stack Business.
SYSTEM_API_KEY=<meme valeur que SECRET_API_KEY cote Business>

# --- Services externes ---
GEMINI_API_KEY=...
SMS_API_BASE_URL=https://mysms.brainsensetech.com/api/v1
SMS_SENDER_NAME=Bconnect
SMS_API_KEY=...
ENV
sudo chmod 600 .env

Toutes les variables marquées obligatoires dans docker-compose.prod.yml font échouer le docker compose up avec un message nommant la variable manquante, plutôt que de démarrer avec une valeur change-me.

Mot de passe MySQL : il est injecté dans une URL (DATABASE_URL). S'il contient @, :, /, # ou %, encodez-le en pourcent (@%40) ou changez-le pour une valeur alphanumérique.

Secrets encore dans le dépôt : compose.yaml contient des clés OpenAI, Gemini et un mot de passe SMTP en clair, et config/jwt/*.pem est versionné avec sa passphrase dans .env. Rien de tout cela ne part en production (.dockerignore les exclut, l'entrypoint génère de nouvelles clés), mais ces valeurs sont dans l'historique git et doivent être révoquées.

5. Traefik

Le conteneur ne publie aucun port : Traefik l'atteint par traefik-net. Les labels sont dans docker-compose.prod.yml ; trois valeurs sont pilotees par le .env :

Variable Défaut
TRAEFIK_ENTRYPOINT websecure entrypoint Traefik
TRAEFIK_CERTRESOLVER letsencrypt résolveur ACME
DOCS_HOST docs.bconnect-pos.169.58.138.150.nip.io hote du portail docs

Si votre Traefik ne termine pas le TLS (Cloudflare ou proxy amont devant), mettez TRAEFIK_ENTRYPOINT=web et retirez les deux labels …tls… du fichier compose : une variable ne suffit pas à supprimer un label.

Le label traefik.docker.network=traefik-net est indispensable ici — le conteneur est sur trois réseaux, et sans lui Traefik peut router vers la mauvaise IP.

Le portail docs est servi par le service separe bconnect-pos-docs, avec nginx sur le port interne 80. Son image est construite avec le meme tag que l'image applicative et injectee au deploiement via DOCS_IMAGE.

6. Vérifier un déploiement

scripts/check-deploy.sh regroupe ces contrôles et y ajoute ce qui ne se voit pas dans docker compose ps : que les deux stacks partagent bien bconnect-net et s'y résolvent mutuellement, et que SECRET_API_KEY (Business) et SYSTEM_API_KEY (POS) portent la même valeur.

bash scripts/check-deploy.sh
cd /srv/apps/BConnect-POS
docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs -f bconnect-pos
docker inspect --format '{{.Config.Image}}' bconnect-pos

La dernière commande affiche le tag exact en service — utile pour confirmer que le déploiement a bien pris.

7. Revenir en arrière

Relancez Run workflow en renseignant image_tag avec le sha-… de la version précédente (visible dans Harbor, ou dans le résumé de l'exécution qui l'avait déployée). Le build est sauté, seul le déploiement s'exécute.

8. Points d'attention

  • Le schéma est mis à jour au démarrage par doctrine:schema:update --force, pas par les migrations — c'est le comportement historique de docker/docker.sh. Sur une base peuplée, cette commande peut proposer des changements destructeurs : à surveiller lors du premier déploiement, et à remplacer par doctrine:migrations:migrate quand la base de production sera établie.
  • Clés JWT : celles du dépôt sont exclues de l'image. L'entrypoint en génère de nouvelles au premier démarrage dans le volume pos_jwt avec JWT_PASSPHRASE. Changer cette passphrase après coup rend les clés existantes illisibles : il faut alors supprimer le volume, ce qui révoque tous les jetons en circulation.
  • Volume pos_uploads : photos produits, profils et documents. Il survit aux déploiements et n'est jamais recréé par le workflow ; sa sauvegarde reste à votre charge. La base MySQL, elle, est sauvegardée avec l'instance mutualisée.
  • LOCK_DSN=flock est local au conteneur : si la stack est un jour répliquée, basculez sur le Redis mutualisé (redis://redis:6379), sinon les verrous ne sont plus partagés entre les instances.
  • docker image prune -f est exécuté après chaque déploiement pour éviter que le disque ne se remplisse d'images obsolètes. Il ne touche qu'aux images sans conteneur associé.